Compare commits

...

5 Commits

Author SHA1 Message Date
Jon Leopard
8f41e08bc6 fix: update pre-push hook to support both Sail and K8s environments 2025-10-31 12:20:07 -07:00
Jon Leopard
2c82099bdd feat: add k-test command for running tests in k8s pod
Adds 'make k-test' command to run tests inside k8s pod, mirroring
the Sail 'make dev-test' workflow. This allows developers to run
tests before pushing without needing Sail running.

Usage:
  make k-test    # Run all tests in k8s pod
2025-10-31 12:08:22 -07:00
Jon Leopard
dd967ff223 fix: update k8s local dev to match Sail workflow with production parity
## Major Changes

**Deployment Manifest (k8s/local/deployment.yaml):**
- Switch from PHP 8.2 to PHP 8.3 (matches production Dockerfile)
- Add PHP_EXTENSIONS env var for intl, pdo_pgsql, pgsql, redis, gd, zip, bcmath
- Set ABSOLUTE_APACHE_DOCUMENT_ROOT to /var/www/html/public
- Remove init container (Sail-like approach: composer runs in main container)
- Add composer install, npm install, and npm build to startup script
- Use TCP connection checks instead of pg_isready/redis-cli (not in image)
- Increase health check delays and failure thresholds for slower startup

**Makefile:**
- Read DB_USERNAME, DB_PASSWORD, DB_DATABASE from .env (not hardcoded)
- PostgreSQL credentials now match .env for consistent auth

**DNS Setup Script:**
- Add scripts/setup-local-dns.sh for one-time dnsmasq configuration
- Idempotent script that's safe to run multiple times
- Works on macOS with Homebrew dnsmasq

## Architecture

Now fully Sail-like:
- Code volume-mounted from worktree (instant changes)
- Composer/npm run inside container at startup
- No pre-installation needed on host
- Each worktree = isolated k8s namespace
- Database credentials from .env (like Sail)

## Testing

Startup sequence verified:
1. Wait for PostgreSQL + Redis
2. Composer install
3. npm install + build
4. Migrations
5. Cache clearing
6. Apache starts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 11:13:05 -07:00
Jon Leopard
569e84562e docs: add comprehensive next steps guide for QR code and k8s features 2025-10-31 10:21:51 -07:00
Jon Leopard
a51398a336 feat: add k8s local development setup with git worktree support
Adds Kubernetes local development environment that mirrors Laravel Sail workflow
with namespace isolation per git worktree.

## Features

**K8s Manifests (k8s/local/):**
- Namespace configuration with worktree labels
- PostgreSQL StatefulSet with PVC (isolated per namespace)
- Redis Deployment
- Laravel app Deployment using Sail-like image with volume mounts
- Service exposing ports 80 and 5173 (Vite)
- Ingress with wildcard routing (*.cannabrands.test)

**Makefile Targets (k- prefix):**
- `make k-dev` - Start k8s environment (auto-detects branch/namespace)
- `make k-down` - Stop k8s environment
- `make k-logs` - View app logs
- `make k-shell` - Shell into app container
- `make k-artisan CMD="..."` - Run artisan commands
- `make k-composer CMD="..."` - Run composer
- `make k-vite` - Start Vite dev server in pod
- `make k-status` - Show namespace status

**Documentation:**
- docs/K8S_LOCAL_SETUP.md - Complete setup guide
- docs/K8S_LIKE_SAIL.md - Philosophy and implementation details

## Architecture

Uses Sail-like approach:
- Pre-built PHP 8.2 image with Apache and Node.js
- Code volume-mounted from worktree (instant changes, no rebuilds)
- Each worktree = isolated k8s namespace
- Custom domain per feature: [branch].cannabrands.test

## Workflow

```bash
# One-time k3d setup (see docs/K8S_LOCAL_SETUP.md)

# Per-worktree usage
cd .worktrees/feature-name
make k-dev              # Start isolated k8s env
# Code changes are instant!
make k-down             # Cleanup
```

Follows Laravel community best practice: fast local dev (Sail-like) with
production parity testing in staging cluster.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 10:20:40 -07:00
13 changed files with 1818 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/sh
#
# Pre-push hook - Runs tests before pushing
# Pre-push hook - Runs tests before pushing (supports both Sail and K8s)
# Can be skipped with: git push --no-verify
#
@@ -8,11 +8,48 @@ echo "🧪 Running tests before push..."
echo " (Use 'git push --no-verify' to skip)"
echo ""
# Run tests
./vendor/bin/sail artisan test --parallel
# Detect which environment is running
SAIL_RUNNING=false
K8S_RUNNING=false
# Check exit code
if [ $? -ne 0 ]; then
# Check if Sail is running
if docker ps --format '{{.Names}}' | grep -q "sail" 2>/dev/null; then
SAIL_RUNNING=true
echo "📦 Detected Sail environment"
fi
# Check if k8s namespace exists for this worktree
BRANCH=$(git rev-parse --abbrev-ref HEAD)
K8S_NS=$(echo "$BRANCH" | sed 's/feature\//feat-/' | sed 's/bugfix\//fix-/' | sed 's/\//-/g')
if kubectl get namespace "$K8S_NS" >/dev/null 2>&1; then
K8S_RUNNING=true
echo "☸️ Detected K8s environment (namespace: $K8S_NS)"
fi
# Run tests in appropriate environment
if [ "$SAIL_RUNNING" = true ]; then
./vendor/bin/sail artisan test --parallel
TEST_EXIT_CODE=$?
elif [ "$K8S_RUNNING" = true ]; then
echo " Running tests in k8s pod..."
kubectl -n "$K8S_NS" exec deploy/web -- php artisan test
TEST_EXIT_CODE=$?
else
echo "⚠️ No environment running (Sail or K8s)"
echo " Skipping tests - please run tests manually"
echo ""
read -p "Continue push anyway? (y/n) " -n 1 -r
echo ""
if [ ! "$REPLY" = "y" ] && [ ! "$REPLY" = "Y" ]; then
echo "Push aborted"
exit 1
fi
exit 0
fi
# Check test results
if [ $TEST_EXIT_CODE -ne 0 ]; then
echo ""
echo "❌ Tests failed!"
echo ""

View File

@@ -1,8 +1,23 @@
.PHONY: help dev dev-down dev-build dev-shell dev-logs dev-vite prod-build prod-up prod-down prod-logs prod-shell prod-vite prod-test prod-test-build prod-test-up prod-test-down prod-test-logs prod-test-shell prod-test-status prod-test-clean migrate test clean install
.PHONY: help dev dev-down dev-build dev-shell dev-logs dev-vite k-dev k-down k-logs k-shell k-artisan k-composer k-vite k-status prod-build prod-up prod-down prod-logs prod-shell prod-vite prod-test prod-test-build prod-test-up prod-test-down prod-test-logs prod-test-shell prod-test-status prod-test-clean migrate test clean install
# Default target
.DEFAULT_GOAL := help
# ==================== K8s Variables ====================
# Auto-detect worktree name from current directory
WORKTREE_NAME := $(shell basename $(CURDIR))
# Generate namespace from branch name (feat-branch-name)
CURRENT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
K8S_NS := $(shell echo "$(CURRENT_BRANCH)" | sed 's/feature\//feat-/' | sed 's/bugfix\//fix-/' | sed 's/\//-/g')
# Generate sanitized branch name for database
SANITIZED_BRANCH := $(shell echo "$(CURRENT_BRANCH)" | sed 's/[^a-zA-Z0-9]/_/g')
# Generate host from branch
K8S_HOST := $(shell echo "$(CURRENT_BRANCH)" | sed 's/feature\///' | sed 's/bugfix\///' | sed 's/\//-/g').cannabrands.test
# Read database credentials from .env
DB_USERNAME := $(shell grep '^DB_USERNAME=' .env 2>/dev/null | cut -d '=' -f2)
DB_PASSWORD := $(shell grep '^DB_PASSWORD=' .env 2>/dev/null | cut -d '=' -f2)
DB_DATABASE := $(shell grep '^DB_DATABASE=' .env 2>/dev/null | cut -d '=' -f2)
# ==================== Local Development (Sail) ====================
dev: ## Start local development environment with Sail
./vendor/bin/sail up -d
@@ -31,6 +46,81 @@ dev-composer: ## Run composer command (usage: make dev-composer CMD="install")
dev-vite: ## Start Vite dev server (run after 'make dev')
./vendor/bin/sail npm run dev
# ==================== K8s Local Development ====================
k-dev: ## Start k8s local environment (like Sail, but with namespace isolation)
@echo "🚀 Starting k8s environment for: $(WORKTREE_NAME)"
@echo " Namespace: $(K8S_NS)"
@echo " Branch: $(CURRENT_BRANCH)"
@echo " URL: http://$(K8S_HOST)"
@echo ""
@# Create namespace
@kubectl create ns $(K8S_NS) --dry-run=client -o yaml | kubectl apply -f -
@# Create secrets from .env
@kubectl -n $(K8S_NS) delete secret app-env --ignore-not-found
@kubectl -n $(K8S_NS) create secret generic app-env --from-env-file=.env
@# Create PostgreSQL auth secret (using credentials from .env)
@kubectl -n $(K8S_NS) create secret generic pg-auth --dry-run=client -o yaml \
--from-literal=POSTGRES_DB=$(DB_DATABASE) \
--from-literal=POSTGRES_USER=$(DB_USERNAME) \
--from-literal=POSTGRES_PASSWORD=$(DB_PASSWORD) | kubectl apply -f -
@# Deploy PostgreSQL
@export NS=$(K8S_NS) PG_DB=$(DB_DATABASE) PG_USER=$(DB_USERNAME) PG_PASS=$(DB_PASSWORD) && \
envsubst < k8s/local/postgres.yaml | kubectl apply -f -
@# Deploy Redis
@export NS=$(K8S_NS) && \
envsubst < k8s/local/redis.yaml | kubectl apply -f -
@# Wait for DB
@echo "⏳ Waiting for PostgreSQL..."
@kubectl -n $(K8S_NS) wait --for=condition=ready pod -l app=postgres --timeout=60s
@# Deploy app (with code volume mounted)
@export NS=$(K8S_NS) WORKTREE_NAME=$(WORKTREE_NAME) K8S_HOST=$(K8S_HOST) && \
envsubst < k8s/local/deployment.yaml | kubectl apply -f -
@# Create service + ingress
@export NS=$(K8S_NS) K8S_HOST=$(K8S_HOST) && \
envsubst < k8s/local/service.yaml | kubectl apply -f - && \
envsubst < k8s/local/ingress.yaml | kubectl apply -f -
@echo ""
@echo "✅ Ready! Visit: http://$(K8S_HOST)"
@echo ""
@echo "💡 Your code is volume-mounted - changes are instant!"
@echo " Edit files → refresh browser → see changes"
@echo ""
@echo "📝 Useful commands:"
@echo " make k-logs # View app logs"
@echo " make k-shell # Open shell in pod"
@echo " make k-vite # Start Vite dev server"
k-down: ## Stop k8s environment
@echo "🗑 Removing namespace: $(K8S_NS)"
@kubectl delete ns $(K8S_NS) --ignore-not-found
@echo "✅ Cleaned up"
k-logs: ## View app logs
@kubectl -n $(K8S_NS) logs -f deploy/web --all-containers=true
k-shell: ## Shell into app container
@kubectl -n $(K8S_NS) exec -it deploy/web -- /bin/bash
k-artisan: ## Run artisan command (usage: make k-artisan CMD="migrate")
@kubectl -n $(K8S_NS) exec deploy/web -- php artisan $(CMD)
k-composer: ## Run composer (usage: make k-composer CMD="install")
@kubectl -n $(K8S_NS) exec deploy/web -- composer $(CMD)
k-vite: ## Run Vite dev server in k8s pod
@echo "🎨 Starting Vite dev server in pod..."
@echo " Access at: http://vite.$(K8S_HOST)"
@kubectl -n $(K8S_NS) exec deploy/web -- npm run dev
k-test: ## Run tests in k8s pod
@echo "🧪 Running tests in k8s pod..."
@kubectl -n $(K8S_NS) exec deploy/web -- php artisan test
k-status: ## Show k8s environment status
@echo "📊 Status for namespace: $(K8S_NS)"
@echo ""
@kubectl -n $(K8S_NS) get pods,svc,ingress
# ==================== Production ====================
prod-build: ## Build production Docker image
docker build -t cannabrands/app:latest -f Dockerfile .
@@ -136,6 +226,8 @@ help: ## Show this help message
@echo "\n📦 CannaBrands Docker Commands\n"
@echo "Local Development (Sail):"
@grep -E '^dev.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo "\nK8s Local Development:"
@grep -E '^k-.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[35m%-25s\033[0m %s\n", $$1, $$2}'
@echo "\nProduction Testing (Local):"
@grep -E '^prod-test.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[33m%-25s\033[0m %s\n", $$1, $$2}'
@echo "\nProduction (K8s/Deployment):"

340
NEXT_STEPS.md Normal file
View File

@@ -0,0 +1,340 @@
# Next Steps: QR Code Feature & K8s Local Dev Setup
This document outlines the next steps for both features that were separated into different git worktrees.
---
## Summary of Work Done
### ✅ QR Code Feature (feature/batch-tracking-coa-qr branch)
**Location:** `.worktrees/labs-batch-qr-codes/`
**Status:** Committed and ready for testing/PR
**Changes:**
- QR code generation endpoints in BatchController
- Filament actions for QR code management
- UI for QR code display in batch views and public COA
- Comprehensive test suite
- Routes for single and bulk operations
### ✅ K8s Local Dev Setup (feature/k8s-local-dev branch)
**Location:** `.worktrees/infra-k8s-local-dev/`
**Status:** Committed and ready for setup/testing
**Changes:**
- K8s manifests for local development (k8s/local/)
- Makefile targets with `k-` prefix
- Complete documentation (docs/K8S_*.md)
---
## Next Steps
### For QR Code Feature
**1. Test the Implementation**
```bash
cd /Users/jon/projects/cannabrands/cannabrands_new/.worktrees/labs-batch-qr-codes
# Start services if not already running
docker-compose up -d pgsql redis
# Run migrations (via Sail if Redis extension issue persists)
./vendor/bin/sail up -d
./vendor/bin/sail artisan migrate
# Run tests
./vendor/bin/sail artisan test --filter=QrCodeGenerationTest
# Or run all tests
./vendor/bin/sail artisan test
```
**2. Manual Testing**
```bash
# Start Sail environment
./vendor/bin/sail up -d
# Access app
open http://localhost
# Test:
# - Navigate to a batch in Filament
# - Generate QR code
# - Download QR code
# - View public COA (should show QR)
# - Test bulk generation
```
**3. Create Pull Request**
```bash
# Push branch to remote
git push origin feature/batch-tracking-coa-qr
# Create PR via GitHub CLI or web interface
gh pr create --title "feat: Add QR code generation for batches" \
--body "$(cat <<EOF
## Summary
Implements QR code generation for batches with download and bulk operations.
## Features
- Single and bulk QR code generation
- Download functionality
- Public COA display
- Business ownership validation
- Comprehensive test suite
## Testing
- [ ] All tests pass
- [ ] Manual testing completed
- [ ] QR codes generate correctly
- [ ] Download works
- [ ] Public COA displays QR
## Screenshots
[Add screenshots of QR generation and COA display]
EOF
)"
```
---
### For K8s Local Dev Setup
**1. One-Time K3d Setup**
Follow the complete guide in `docs/K8S_LOCAL_SETUP.md`:
```bash
# Install tools (macOS)
brew install k3d kubectl mkcert dnsmasq
# Create k3d cluster with volume access
k3d cluster create dev \
--agents 2 \
-p "80:80@loadbalancer" \
-p "443:443@loadbalancer" \
--volume "$HOME/projects/cannabrands_new/.worktrees:/worktrees@all"
# Setup wildcard DNS (*.cannabrands.test)
echo 'address=/.cannabrands.test/127.0.0.1' | \
sudo tee /opt/homebrew/etc/dnsmasq.d/cannabrands.conf
sudo brew services start dnsmasq
sudo mkdir -p /etc/resolver
echo 'nameserver 127.0.0.1' | \
sudo tee /etc/resolver/cannabrands.test
# Test DNS
ping -c 1 anything.cannabrands.test
# Should resolve to 127.0.0.1
```
**2. Test K8s Setup in This Worktree**
```bash
cd /Users/jon/projects/cannabrands/cannabrands_new/.worktrees/infra-k8s-local-dev
# Copy .env from another worktree
cp ../ labs-batch-qr-codes/.env .
# Install dependencies (or link vendor from main)
composer install
# Start k8s environment
make k-dev
# Expected output:
# - Namespace created: feat-k8s-local-dev
# - Services deployed: PostgreSQL, Redis, Laravel app
# - URL: http://k8s-local-dev.cannabrands.test
# Check status
make k-status
# View logs
make k-logs
# Open shell
make k-shell
# Cleanup
make k-down
```
**3. Test with Multiple Worktrees**
```bash
# Terminal 1: QR Code feature
cd /Users/jon/projects/cannabrands/cannabrands_new/.worktrees/labs-batch-qr-codes
make k-dev
# → http://labs-batch-qr-codes.cannabrands.test
# Terminal 2: K8s dev feature
cd /Users/jon/projects/cannabrands/cannabrands_new/.worktrees/infra-k8s-local-dev
make k-dev
# → http://k8s-local-dev.cannabrands.test
# Both running simultaneously with isolated namespaces!
```
**4. Create Pull Request**
```bash
cd /Users/jon/projects/cannabrands/cannabrands_new/.worktrees/infra-k8s-local-dev
# Push branch
git push origin feature/k8s-local-dev
# Create PR
gh pr create --title "feat: Add K8s local development with worktree support" \
--body "$(cat <<EOF
## Summary
Adds Kubernetes local development environment that mirrors Laravel Sail
workflow with namespace isolation per git worktree.
## Features
- K8s manifests for local dev (Sail-like approach)
- Makefile targets with \`k-\` prefix
- Complete documentation
- Namespace isolation per worktree
- Wildcard domain routing (*.cannabrands.test)
## Setup Required
- One-time k3d cluster setup (see docs/K8S_LOCAL_SETUP.md)
- DNS configuration for *.cannabrands.test
## Testing
- [ ] k3d cluster created
- [ ] DNS resolves *.cannabrands.test
- [ ] \`make k-dev\` starts successfully
- [ ] App accessible at custom domain
- [ ] Multiple worktrees work in parallel
- [ ] Code changes are instant (no rebuild)
## Documentation
- docs/K8S_LOCAL_SETUP.md - Complete setup guide
- docs/K8S_LIKE_SAIL.md - Philosophy and details
EOF
)"
```
**5. Update Team Documentation**
After PR is merged:
- Add k8s setup instructions to main README
- Announce new workflow to team
- Consider team demo/walkthrough
---
## Workflow After Both Features Are Merged
### Standard Development Flow
```bash
# Create new feature
cd /Users/jon/projects/cannabrands/cannabrands_new
git worktree add .worktrees/feature-orders feature/orders
cd .worktrees/feature-orders
# Option 1: Sail (existing workflow)
make dev
make dev-vite
# Option 2: K8s (new workflow with isolation)
make k-dev
make k-vite
# Develop with instant code changes...
# Cleanup
make dev-down # or make k-down
cd ../..
git worktree remove .worktrees/feature-orders
```
### When to Use Each Mode
**Use Sail (`make dev`):**
- Quick single-feature development
- Familiar workflow
- Lower resource usage
**Use K8s (`make k-dev`):**
- Multiple features in parallel
- Testing k8s manifests
- Production-like environment
- Need custom domain per feature
---
## Troubleshooting
### QR Code Feature Issues
**Redis extension error:**
```bash
# Solution: Use Sail instead of host PHP
./vendor/bin/sail artisan migrate
./vendor/bin/sail artisan test
```
**Tests fail:**
```bash
# Ensure services are running
docker ps | grep postgres
docker ps | grep redis
# Check .env configuration
DB_HOST=127.0.0.1 # NOT pgsql (hybrid mode)
REDIS_HOST=127.0.0.1 # NOT redis
```
### K8s Setup Issues
**DNS not working:**
```bash
# Flush DNS cache
sudo killall -HUP mDNSResponder
sudo brew services restart dnsmasq
# Test
dig k8s-local-dev.cannabrands.test
```
**Pod crashes:**
```bash
# Check logs
make k-logs
# Common issues:
# - Volume mount path incorrect
# - Missing dependencies (run composer install in pod)
# - DB connection (check PostgreSQL is ready)
```
**Port conflicts:**
```bash
# Check what's using ports 80/443
lsof -ti:80
lsof -ti:443
# Stop conflicting services
./vendor/bin/sail down # if Sail is running
```
---
## Questions?
- Check documentation: docs/K8S_LOCAL_SETUP.md and docs/K8S_LIKE_SAIL.md
- Review git worktree workflow: docs/GIT_WORKTREE_WORKFLOW.md
- Ask in team chat
---
**Generated:** 2025-10-31
**Author:** Claude Code with Human Partner

539
docs/K8S_LIKE_SAIL.md Normal file
View File

@@ -0,0 +1,539 @@
# K8s Local Development (Sail-Style - No Image Builds)
**Goal:** Use k8s for local dev exactly like Laravel Sail - no image building, instant code changes.
---
## Philosophy: Development Should Be Fast
### How Sail Works (and Why We Love It)
```bash
./vendor/bin/sail up
# ✅ Uses pre-built PHP image
# ✅ Mounts your code as volume
# ✅ Code changes are instant
# ✅ No docker build needed
# ✅ Composer/artisan run inside container
```
### How K8s Should Work (Same Experience)
```bash
make k8s-up
# ✅ Uses pre-built PHP image
# ✅ Mounts your code from worktree
# ✅ Code changes are instant
# ✅ No docker build needed
# ✅ Composer/artisan run inside pod
```
**The only difference:** k8s gives you namespace isolation + nice URLs.
---
## Architecture
### Sail Setup
```yaml
# docker-compose.yml (simplified)
services:
laravel.test:
image: sail-8.2/app # Pre-built
volumes:
- './:/var/www/html' # Code mounted
depends_on:
- pgsql
- redis
```
### K8s Setup (Same Concept)
```yaml
# k8s deployment (simplified)
spec:
containers:
- name: web
image: thecodingmachine/php:8.2-apache # Pre-built
volumeMounts:
- name: code
mountPath: /var/www/html # Code mounted from worktree
volumes:
- name: code
hostPath:
path: /worktrees/feature-name # Your worktree
```
**Both mount code from disk → instant changes!**
---
## Key Insight: Base Images vs Custom Images
### Base Image (For Development) ✅
```dockerfile
# Use existing image (like Sail)
thecodingmachine/php:8.2-apache
# or
laravelsail/php82-composer:latest
```
**Includes:**
- PHP 8.2
- Apache/Nginx
- Composer
- Common extensions (pgsql, redis, gd, etc.)
- Node.js (for Vite)
**Mount your code** → no build needed!
### Custom Image (For Production) 🚢
```dockerfile
# Build your own (for deployment)
FROM php:8.2-fpm-alpine
COPY . /var/www/html
RUN composer install --no-dev
# etc...
```
**Only build this for:**
- CI/CD testing
- Production deployment
- Final PR verification
---
## Complete K8s Setup (Sail-Style)
### 1. Create k3d Cluster with Volume Access
```bash
# One-time setup
k3d cluster create dev \
--agents 2 \
-p "80:80@loadbalancer" \
-p "443:443@loadbalancer" \
--volume "$HOME/projects/cannabrands_new/.worktrees:/worktrees@all"
```
This mounts your `.worktrees/` directory into all k3d nodes.
### 2. K8s Manifests (No Image Build)
**k8s/local/deployment.yaml**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: ${NS}
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
# Run composer install on startup (like Sail)
initContainers:
- name: composer-install
image: composer:2
workingDir: /var/www/html
command: ['composer', 'install', '--no-interaction']
volumeMounts:
- name: code
mountPath: /var/www/html
containers:
- name: web
image: thecodingmachine/php:8.2-apache
ports:
- containerPort: 80
workingDir: /var/www/html
# Environment variables
envFrom:
- secretRef:
name: app-env
# Your code mounted from worktree
volumeMounts:
- name: code
mountPath: /var/www/html
# Startup command (like Sail)
command: ["/bin/bash", "-c"]
args:
- |
# Wait for DB
until pg_isready -h postgres -U ${DB_USERNAME}; do
echo "Waiting for PostgreSQL..."
sleep 2
done
# Run migrations (like Sail auto-migration)
php artisan migrate --force
# Start Apache
apache2-foreground
# Health check
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
# Mount code from k3d volume (which mounts your worktree)
volumes:
- name: code
hostPath:
path: /worktrees/${WORKTREE_NAME}
type: Directory
```
**k8s/local/postgres.yaml** (same as before)
```yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: ${NS}
spec:
selector:
app: postgres
ports:
- port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: ${NS}
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: pg-auth
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
subPath: data
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
```
**k8s/local/redis.yaml**
```yaml
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: ${NS}
spec:
selector:
app: redis
ports:
- port: 6379
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: ${NS}
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
```
### 3. Makefile (One Command Like Sail)
```makefile
k8s-up: ## Start k8s environment (like 'sail up')
@echo "🚀 Starting k8s environment for: $(WORKTREE_NAME)"
@# Create namespace
@kubectl create ns $(K8S_NS) --dry-run=client -o yaml | kubectl apply -f -
@# Create secrets from .env
@kubectl -n $(K8S_NS) delete secret app-env --ignore-not-found
@kubectl -n $(K8S_NS) create secret generic app-env --from-env-file=.env
@# Create PostgreSQL
@export NS=$(K8S_NS) PG_DB=app_$(SANITIZED_BRANCH) PG_USER=app PG_PASS=secret && \
envsubst < k8s/local/postgres.yaml | kubectl apply -f -
@# Create Redis
@export NS=$(K8S_NS) && \
envsubst < k8s/local/redis.yaml | kubectl apply -f -
@# Wait for DB
@kubectl -n $(K8S_NS) rollout status sts/postgres --timeout=60s
@# Deploy app (with code volume mounted)
@export NS=$(K8S_NS) WORKTREE_NAME=$(WORKTREE_NAME) && \
envsubst < k8s/local/deployment.yaml | kubectl apply -f -
@# Create service + ingress
@export NS=$(K8S_NS) HOST=$(K8S_HOST) && \
envsubst < k8s/local/service.yaml | kubectl apply -f - && \
envsubst < k8s/local/ingress.yaml | kubectl apply -f -
@echo ""
@echo "✅ Ready! Visit: http://$(K8S_HOST)"
@echo ""
@echo "💡 Your code is volume-mounted - changes are instant!"
@echo " Edit files → refresh browser → see changes"
k8s-down: ## Stop k8s environment (like 'sail down')
@echo "🗑 Removing namespace: $(K8S_NS)"
@kubectl delete ns $(K8S_NS) --ignore-not-found
@echo "✅ Cleaned up"
k8s-logs: ## View app logs (like 'sail logs')
@kubectl -n $(K8S_NS) logs -f deploy/web
k8s-shell: ## Shell into app container (like 'sail shell')
@kubectl -n $(K8S_NS) exec -it deploy/web -- /bin/bash
k8s-artisan: ## Run artisan command (like 'sail artisan')
@kubectl -n $(K8S_NS) exec deploy/web -- php artisan $(CMD)
k8s-composer: ## Run composer (like 'sail composer')
@kubectl -n $(K8S_NS) exec deploy/web -- composer $(CMD)
k8s-npm: ## Run npm (like 'sail npm')
@kubectl -n $(K8S_NS) exec deploy/web -- npm $(CMD)
```
---
## Usage Comparison
### Sail Commands
```bash
./vendor/bin/sail up -d
./vendor/bin/sail artisan migrate
./vendor/bin/sail composer install
./vendor/bin/sail npm run dev
./vendor/bin/sail shell
./vendor/bin/sail down
```
### K8s Commands (Same Experience)
```bash
make k8s-up
make k8s-artisan CMD=migrate
make k8s-composer CMD=install
make k8s-npm CMD="run dev"
make k8s-shell
make k8s-down
```
**Nearly identical workflow!**
---
## Development Workflow
### 1. Start Feature
```bash
git worktree add .worktrees/feature-orders feature/orders
cd .worktrees/feature-orders
```
### 2. Start K8s Environment (Like Sail)
```bash
make k8s-up
# → Creates namespace
# → Starts PostgreSQL, Redis
# → Mounts YOUR CODE from worktree
# → Runs migrations
# → Available at: http://orders.cannabrands.test
```
### 3. Develop (Instant Changes)
```bash
# Edit code in your IDE
vim app/Http/Controllers/OrderController.php
# Changes are INSTANT (volume mounted)
# Refresh browser → see changes ✅
# Run commands in pod
make k8s-artisan CMD="make:model Product"
make k8s-composer CMD="require package/name"
make k8s-npm CMD="install"
```
### 4. Multiple Features (Parallel)
```bash
# Terminal 1: Feature A
cd .worktrees/feature-orders
make k8s-up
# → http://orders.cannabrands.test
# Terminal 2: Feature B
cd .worktrees/feature-payments
make k8s-up
# → http://payments.cannabrands.test
# Both running, completely isolated! ✅
```
### 5. Cleanup
```bash
make k8s-down
# → Deletes namespace
# → Removes PostgreSQL, Redis
# → That's it! ✅
```
---
## Benefits Over Sail
| Feature | Sail | K8s |
|---------|------|-----|
| **Code volume mounted** | ✅ | ✅ |
| **No image builds** | ✅ | ✅ |
| **Instant code changes** | ✅ | ✅ |
| **Isolated environments** | ❌ (shared services) | ✅ (namespaces) |
| **Custom domains** | ❌ (localhost ports) | ✅ (*.cannabrands.test) |
| **Test k8s configs** | ❌ | ✅ |
| **Production parity** | ❌ | ✅ |
| **Multiple features parallel** | ⚠️ (port conflicts) | ✅ (easy) |
---
## When You DO Build Images
**Only build images for:**
1. **CI/CD Testing**
```bash
# In GitHub Actions
docker build -t cannabrands:$BRANCH .
# Test the build works
```
2. **Production Deployment**
```bash
# For production k8s cluster
docker build -t registry.example.com/cannabrands:v1.2.3 .
docker push registry.example.com/cannabrands:v1.2.3
```
3. **Final PR Verification** (optional)
```bash
# Test production Dockerfile locally
make k8s-test-prod-build
```
**But for daily feature development:** NO IMAGE BUILDS! ✅
---
## Troubleshooting
### Code Changes Not Showing
```bash
# 1. Check volume mount
kubectl -n $(K8S_NS) exec deploy/web -- ls -la /var/www/html
# Should show your worktree files
# 2. Clear Laravel caches
make k8s-artisan CMD="cache:clear"
make k8s-artisan CMD="config:clear"
make k8s-artisan CMD="view:clear"
# 3. Check file permissions (rare)
kubectl -n $(K8S_NS) exec deploy/web -- chown -R www-data:www-data /var/www/html/storage
```
### Composer Install Fails
```bash
# Run composer inside pod (has all PHP extensions)
make k8s-composer CMD="install"
# If needed, clear cache
make k8s-composer CMD="clear-cache"
```
### Database Connection Error
```bash
# Check PostgreSQL is ready
kubectl -n $(K8S_NS) get pods
# If not ready, wait
kubectl -n $(K8S_NS) rollout status sts/postgres
# Check connection from app
make k8s-shell
# Inside pod:
php artisan tinker
>>> DB::connection()->getPdo();
```
---
## Summary
**K8s local dev can work EXACTLY like Sail:**
1. ✅ Use pre-built PHP image (no custom builds)
2. ✅ Mount code as volumes (instant changes)
3. ✅ Run composer/artisan inside pods
4. ✅ One command to start/stop (`make k8s-up/down`)
**Bonus benefits:**
- 🎯 Namespace isolation (multiple features parallel)
- 🌐 Nice URLs (*.cannabrands.test)
- 🚀 Tests k8s configs locally
- 🏢 Production parity
**The workflow IS sail, just orchestrated by k8s instead of docker-compose.**
---
**Next Steps:**
1. Run one-time k3d cluster setup
2. Test with one worktree: `make k8s-up`
3. Edit code → see instant changes
4. Use for all feature development ✅

370
docs/K8S_LOCAL_SETUP.md Normal file
View File

@@ -0,0 +1,370 @@
# Local Kubernetes Development with k3d + Git Worktrees
**Goal:** Each git worktree gets its own isolated k8s namespace at `[branch-name].cannabrands.test`
---
## One-Time Setup (Do This Once)
### 1. Install Required Tools
```bash
# macOS
brew install k3d kubectl mkcert dnsmasq
# Verify installation
k3d version
kubectl version --client
```
### 2. Create k3d Cluster
```bash
# Create cluster with Traefik ingress
k3d cluster create dev \
--agents 2 \
-p "80:80@loadbalancer" \
-p "443:443@loadbalancer"
# Verify cluster
kubectl cluster-info
kubectl get nodes
```
**What this does:**
- Creates 3-node cluster (1 server, 2 agents)
- Exposes ports 80/443 for web traffic
- Includes Traefik ingress controller (built-in)
### 3. Setup Wildcard DNS (*.cannabrands.test → 127.0.0.1)
```bash
# Configure dnsmasq to resolve *.cannabrands.test
echo 'address=/.cannabrands.test/127.0.0.1' | \
sudo tee /opt/homebrew/etc/dnsmasq.d/cannabrands.conf
# Start dnsmasq
sudo brew services start dnsmasq
# Tell macOS to use dnsmasq for .test domains
sudo mkdir -p /etc/resolver
echo 'nameserver 127.0.0.1' | \
sudo tee /etc/resolver/cannabrands.test
# Test it works
ping -c 1 anything.cannabrands.test
# Should resolve to 127.0.0.1
```
### 4. (Optional) Setup HTTPS with mkcert
```bash
# Install local CA
mkcert -install
# Generate wildcard certificate
mkcert "*.cannabrands.test"
# Create k8s secret (in kube-system for Traefik)
kubectl -n kube-system create secret tls cannabrands-test-tls \
--cert="_wildcard.cannabrands.test.pem" \
--key="_wildcard.cannabrands.test-key.pem"
```
---
## Per-Worktree Usage
### Quick Start
```bash
# From a worktree directory
cd .worktrees/labs-batch-qr-codes
# Spin up k8s environment
make k8s-up
# Access your app
open http://labs-batch-qr-codes.cannabrands.test
# Tear down when done
make k8s-down
```
### What `make k8s-up` Does
1. **Detects branch name** → namespace name
- Branch: `feature/batch-qr-codes`
- Namespace: `feat-batch-qr-codes`
- URL: `batch-qr-codes.cannabrands.test`
2. **Builds Docker image** from current worktree
- Tag: `cannabrands:batch-qr-codes`
- Includes all your code changes
3. **Imports image** into k3d cluster
4. **Creates namespace** with isolated resources:
- PostgreSQL database (with PVC)
- Redis cache
- Laravel app deployment
- Services + Ingress
5. **Runs migrations** automatically
6. **Shows URL** when ready
### What `make k8s-down` Does
1. **Deletes namespace** (removes everything)
2. **Removes Docker image** (cleanup)
3. **That's it!** No orphaned resources
---
## Architecture
### Namespace Isolation
```
k3d cluster "dev"
├── namespace: feat-batch-qr-codes
│ ├── PostgreSQL (1Gi PVC)
│ ├── Redis
│ ├── Laravel app (your code)
│ └── Ingress → batch-qr-codes.cannabrands.test
├── namespace: feat-order-flow
│ ├── PostgreSQL (1Gi PVC)
│ ├── Redis
│ ├── Laravel app (different code)
│ └── Ingress → order-flow.cannabrands.test
└── namespace: kube-system
└── Traefik (shared ingress controller)
```
**Each namespace is completely isolated:**
- ✅ Own database
- ✅ Own Redis
- ✅ Own environment variables
- ✅ Own code version
- ✅ Own URL
### Why This Works
1. **True Production Parity**
- Uses same k8s manifests as production
- Tests deployments, services, ingress
- Catches k8s-specific issues locally
2. **Complete Isolation**
- No shared state between features
- Can't accidentally break other features
- Clean teardown = delete namespace
3. **Git Worktree Perfect Match**
- One worktree = one namespace
- Code changes in worktree → image rebuild
- Delete worktree → delete namespace
4. **Better Than Hybrid for:**
- Testing k8s configs
- Multi-service features
- Production debugging
---
## Comparison: Hybrid vs K8s
### Hybrid Mode (Native Laravel + Docker Compose)
```bash
cd .worktrees/feature-a
php artisan serve --port=8000 # http://localhost:8000
```
**Pros:**
- ✅ Faster startup (~5 seconds)
- ✅ Simpler (no k8s knowledge needed)
- ✅ Lower resource usage
**Cons:**
- ❌ Doesn't test k8s configs
- ❌ Port-based URLs (8000, 8001)
- ❌ Not production-like
### K8s Mode (k3d)
```bash
cd .worktrees/feature-a
make k8s-up # http://feature-a.cannabrands.test
```
**Pros:**
- ✅ Production parity
- ✅ Tests k8s manifests
- ✅ Nice URLs (*.cannabrands.test)
- ✅ Complete isolation
**Cons:**
- ❌ Slower startup (~30 seconds)
- ❌ More resources (Docker overhead)
- ❌ Requires k8s knowledge
### Recommendation
**Use both!**
```bash
# 90% of time: Quick iterations
make serve # Hybrid mode
# 10% of time: Pre-merge testing
make k8s-up # K8s mode
```
---
## Troubleshooting
### DNS Not Working
```bash
# Test resolution
dig anything.cannabrands.test
# Should show:
# ;; ANSWER SECTION:
# anything.cannabrands.test. 0 IN A 127.0.0.1
# If not:
sudo killall -HUP mDNSResponder # Flush DNS cache
sudo brew services restart dnsmasq
```
### k3d Cluster Issues
```bash
# Check cluster status
k3d cluster list
kubectl get nodes
# Restart cluster
k3d cluster stop dev
k3d cluster start dev
# Nuclear option (delete and recreate)
k3d cluster delete dev
k3d cluster create dev --agents 2 -p "80:80@loadbalancer" -p "443:443@loadbalancer"
```
### Image Not Found in Cluster
```bash
# Verify image imported
k3d image list -c dev | grep cannabrands
# Manual import
k3d image import cannabrands:branch-name -c dev
```
### Namespace Stuck Deleting
```bash
# Force delete
kubectl delete ns feat-branch-name --grace-period=0 --force
```
### Port Already in Use
```bash
# Find what's using port 80
lsof -ti:80
# Stop Traefik if needed
kubectl -n kube-system scale deploy/traefik --replicas=0
# Restart
kubectl -n kube-system scale deploy/traefik --replicas=1
```
---
## Advanced Usage
### Share Environment with Teammate
```bash
# Export namespace manifests
kubectl get all -n feat-branch-name -o yaml > my-feature.yaml
# Teammate applies
kubectl apply -f my-feature.yaml
```
### Access Database Directly
```bash
# Port-forward PostgreSQL
kubectl -n feat-batch-qr-codes port-forward svc/postgres 5433:5432
# Connect with client
psql -h localhost -p 5433 -U app -d app_batch_qr_codes
```
### View Logs
```bash
# App logs
kubectl -n feat-batch-qr-codes logs -f deploy/web
# Postgres logs
kubectl -n feat-batch-qr-codes logs -f sts/postgres
# All logs
kubectl -n feat-batch-qr-codes logs -f --all-containers=true -l app=web
```
### Shell into Pod
```bash
# Laravel app
kubectl -n feat-batch-qr-codes exec -it deploy/web -- /bin/bash
# Run artisan commands
kubectl -n feat-batch-qr-codes exec deploy/web -- php artisan migrate
kubectl -n feat-batch-qr-codes exec deploy/web -- php artisan tinker
```
---
## Resource Usage
### Typical k3d Overhead
```
k3d cluster: ~500MB RAM
Per namespace: ~300-500MB RAM
- PostgreSQL: ~100MB
- Redis: ~50MB
- Laravel app: ~150-300MB
```
**Total for 3 features running:** ~2GB RAM
Compare to Hybrid Mode: ~200MB per worktree
---
## Next Steps
1. ✅ Complete one-time setup above
2. ✅ Test with: `make k8s-up` in a worktree
3. ✅ Verify URL works: `http://[branch].cannabrands.test`
4. ✅ Tear down: `make k8s-down`
5. ✅ Add to your daily workflow
---
**Questions?** See `docs/GIT_WORKTREE_WORKFLOW.md` or ask in team chat.

130
k8s/local/deployment.yaml Normal file
View File

@@ -0,0 +1,130 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: ${NS}
labels:
app: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
# Sail-like approach: composer install runs inside container at startup
# No pre-installation needed on host - everything happens in the pod
containers:
# Main Laravel application container
- name: web
image: thecodingmachine/php:8.3-v5-apache-node20
# Note: PHP 8.3 matches production, intl extension available via PHP_EXTENSIONS
ports:
- containerPort: 80
name: http
- containerPort: 5173
name: vite
workingDir: /var/www/html
# Environment variables from .env
envFrom:
- secretRef:
name: app-env
# Additional required environment variables for k8s
env:
- name: DB_HOST
value: postgres
- name: REDIS_HOST
value: redis
- name: APP_URL
value: http://${K8S_HOST}
- name: VITE_APP_URL
value: http://${K8S_HOST}
- name: PHP_EXTENSIONS
value: "intl pdo_pgsql pgsql redis gd zip bcmath"
- name: ABSOLUTE_APACHE_DOCUMENT_ROOT
value: "/var/www/html/public"
# Mount code from worktree via k3d volume
volumeMounts:
- name: code
mountPath: /var/www/html
# Startup command
command: ["/bin/bash", "-c"]
args:
- |
set -e
echo "Waiting for PostgreSQL..."
until timeout 1 bash -c 'cat < /dev/null > /dev/tcp/postgres/5432' 2>/dev/null; do
echo "PostgreSQL is unavailable - sleeping"
sleep 2
done
echo "PostgreSQL is ready!"
echo "Waiting for Redis..."
until timeout 1 bash -c 'cat < /dev/null > /dev/tcp/redis/6379' 2>/dev/null; do
echo "Redis is unavailable - sleeping"
sleep 2
done
echo "Redis is ready!"
echo "Installing/updating composer dependencies..."
composer install --no-interaction --prefer-dist
echo "Installing/updating npm dependencies..."
npm install --no-audit --no-fund
echo "Building frontend assets..."
npm run build
echo "Running migrations..."
php artisan migrate --force
echo "Clearing caches..."
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
echo "Starting Apache..."
apache2-foreground
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 90
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 60
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 6
# Mount code from k3d volume (which mounts your worktree)
volumes:
- name: code
hostPath:
path: /worktrees/${WORKTREE_NAME}
type: Directory

33
k8s/local/ingress.yaml Normal file
View File

@@ -0,0 +1,33 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: ${NS}
annotations:
# Traefik annotations
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
# Main application domain
- host: ${K8S_HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
# Vite HMR domain (for hot module replacement)
- host: vite.${K8S_HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 5173

7
k8s/local/namespace.yaml Normal file
View File

@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: ${NS}
labels:
environment: local
worktree: ${WORKTREE_NAME}

84
k8s/local/postgres.yaml Normal file
View File

@@ -0,0 +1,84 @@
apiVersion: v1
kind: Secret
metadata:
name: pg-auth
namespace: ${NS}
type: Opaque
stringData:
POSTGRES_DB: ${PG_DB}
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: ${NS}
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
clusterIP: None # Headless service for StatefulSet
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: ${NS}
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
name: postgres
envFrom:
- secretRef:
name: pg-auth
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
subPath: data # Prevent PostgreSQL from failing on non-empty directory
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
exec:
command:
- pg_isready
- -U
- ${PG_USER}
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- ${PG_USER}
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi

54
k8s/local/redis.yaml Normal file
View File

@@ -0,0 +1,54 @@
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: ${NS}
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: ${NS}
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
name: redis
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5

20
k8s/local/service.yaml Normal file
View File

@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: web
namespace: ${NS}
labels:
app: web
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: vite
port: 5173
targetPort: 5173
protocol: TCP
type: ClusterIP

2
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "cannabrands_new",
"name": "html",
"lockfileVersion": 3,
"requires": true,
"packages": {

105
scripts/setup-local-dns.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/bin/bash
#
# Local DNS Setup for K8s Development
#
# This script configures *.cannabrands.test to resolve to 127.0.0.1
# Run once per developer machine. Safe to run multiple times (idempotent).
#
# Requirements: dnsmasq installed via Homebrew
#
set -e
echo ""
echo "🌐 Local DNS Setup for *.cannabrands.test"
echo "=========================================="
echo ""
# Check if running on macOS
if [[ "$(uname)" != "Darwin" ]]; then
echo "❌ Error: This script is for macOS only"
exit 1
fi
# Check if dnsmasq is installed
if ! command -v dnsmasq &> /dev/null; then
echo "❌ Error: dnsmasq not found"
echo " Install with: brew install dnsmasq"
exit 1
fi
# 1. Configure dnsmasq
echo "📝 Step 1/4: Configuring dnsmasq..."
DNSMASQ_CONF="/opt/homebrew/etc/dnsmasq.d/cannabrands.conf"
if [[ -f "$DNSMASQ_CONF" ]]; then
echo " Config already exists: $DNSMASQ_CONF"
echo " Current content: $(cat $DNSMASQ_CONF)"
else
sudo mkdir -p /opt/homebrew/etc/dnsmasq.d
echo 'address=/.cannabrands.test/127.0.0.1' | sudo tee "$DNSMASQ_CONF" > /dev/null
echo " ✅ Created: $DNSMASQ_CONF"
fi
# 2. Restart dnsmasq
echo ""
echo "🔄 Step 2/4: Restarting dnsmasq service..."
sudo brew services restart dnsmasq
sleep 2 # Give dnsmasq time to start
# Check if dnsmasq is running
if brew services list | grep -q "dnsmasq.*started"; then
echo " ✅ dnsmasq is running"
else
echo " ⚠️ Warning: dnsmasq may not be running properly"
echo " Check with: brew services list"
fi
# 3. Configure macOS resolver
echo ""
echo "📝 Step 3/4: Configuring macOS resolver..."
RESOLVER_CONF="/etc/resolver/cannabrands.test"
if [[ -f "$RESOLVER_CONF" ]]; then
echo " Resolver already exists: $RESOLVER_CONF"
echo " Current content: $(cat $RESOLVER_CONF)"
else
sudo mkdir -p /etc/resolver
echo 'nameserver 127.0.0.1' | sudo tee "$RESOLVER_CONF" > /dev/null
echo " ✅ Created: $RESOLVER_CONF"
fi
# 4. Flush DNS cache
echo ""
echo "🧹 Step 4/4: Flushing macOS DNS cache..."
sudo killall -HUP mDNSResponder 2>/dev/null || true
echo " ✅ DNS cache flushed"
# Test DNS resolution
echo ""
echo "🧪 Testing DNS resolution..."
sleep 1 # Give resolver time to pick up changes
if ping -c 1 -W 2 test.cannabrands.test &>/dev/null; then
echo " ✅ SUCCESS! test.cannabrands.test resolves to 127.0.0.1"
echo ""
echo "✅ DNS Setup Complete!"
echo ""
echo "All *.cannabrands.test domains now resolve to 127.0.0.1"
echo "You can now use: make k-dev in any worktree"
else
echo " ⚠️ Warning: DNS test failed"
echo ""
echo "Troubleshooting:"
echo "1. Check dnsmasq: brew services list"
echo "2. Test manually: dig test.cannabrands.test"
echo "3. Check config: cat $DNSMASQ_CONF"
echo "4. Check resolver: cat $RESOLVER_CONF"
echo ""
echo "If issues persist, try:"
echo " - Restart your terminal"
echo " - Restart your Mac"
exit 1
fi
echo ""