feat: add local production Docker testing workflow

Add tooling to test production Docker images locally before CI/CD:

- docker-compose.prod-test.yml: Local production test environment
- TESTING_PRODUCTION_LOCALLY.md: Comprehensive testing guide
- Makefile: Added prod-test-* commands for easy testing

New Make commands:
- make prod-test        - Build and run production image
- make prod-test-build  - Build with no cache
- make prod-test-up     - Start in background
- make prod-test-logs   - View logs
- make prod-test-shell  - Debug in container
- make prod-test-status - Check supervisor status
- make prod-test-clean  - Fresh start

Benefits:
- 5-10min faster feedback vs CI/CD builds
- Easier debugging with direct container access
- Catch Docker/config issues before pushing
- Test supervisor, nginx, php-fpm, queue workers locally

Usage: make prod-test

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jon Leopard
2025-10-23 22:31:45 -07:00
parent 0db548d1fe
commit 36eadb3bb4
3 changed files with 295 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
.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 migrate test clean install
.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
# Default target
.DEFAULT_GOAL := help
@@ -56,6 +56,43 @@ prod-artisan: ## Run artisan in production (usage: make prod-artisan CMD="migrat
prod-vite: ## Build production assets (for CI/CD)
./vendor/bin/sail npm run build
# ==================== Production Testing (Local) ====================
prod-test: ## Test production image locally (foreground)
@echo "🔨 Building and testing production image locally..."
docker-compose -f docker-compose.prod-test.yml up --build
prod-test-build: ## Build production test image (no cache)
@echo "🔨 Building production test image..."
docker-compose -f docker-compose.prod-test.yml build --no-cache --pull
prod-test-up: ## Start production test environment (background)
@echo "🚀 Starting production test environment..."
docker-compose -f docker-compose.prod-test.yml up -d
@echo ""
@echo "✅ Production test running!"
@echo " App: http://localhost:8080"
@echo " Database: localhost:5433"
@echo ""
@echo "View logs: make prod-test-logs"
prod-test-down: ## Stop production test environment
docker-compose -f docker-compose.prod-test.yml down
prod-test-logs: ## View production test logs
docker-compose -f docker-compose.prod-test.yml logs -f app
prod-test-shell: ## Open shell in production test container
docker-compose -f docker-compose.prod-test.yml exec app /bin/sh
prod-test-status: ## Check supervisor status in production test
@echo "📊 Supervisor status:"
docker-compose -f docker-compose.prod-test.yml exec app supervisorctl status
prod-test-clean: ## Clean production test environment (removes volumes)
@echo "🧹 Cleaning production test environment..."
docker-compose -f docker-compose.prod-test.yml down -v
@echo "✅ Cleaned! Run 'make prod-test' for fresh start"
# ==================== Database ====================
migrate: ## Run database migrations (Sail)
./vendor/bin/sail artisan migrate
@@ -98,13 +135,15 @@ mailpit: ## Open Mailpit web UI
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%-20s\033[0m %s\n", $$1, $$2}'
@echo "\nProduction:"
@grep -E '^prod.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
@grep -E '^dev.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-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):"
@grep -E '^prod-[^t].*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo "\nDatabase:"
@grep -E '^(migrate|seed).*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
@grep -E '^(migrate|seed).*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo "\nTesting:"
@grep -E '^test.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
@grep -E '^test.*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo "\nUtilities:"
@grep -E '^(clean|install|mailpit).*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
@grep -E '^(clean|install|mailpit).*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo ""

View File

@@ -0,0 +1,174 @@
# Testing Production Docker Image Locally
This guide helps you test the production Docker image locally before pushing to CI/CD.
## Quick Start
```bash
# Build and start the production image locally
docker-compose -f docker-compose.prod-test.yml up --build
# Access the app at: http://localhost:8080
# PostgreSQL accessible at: localhost:5433
```
## Why Test Locally?
- ✅ **Faster feedback** - 2-5 min vs 10-15 min through CI/CD
- ✅ **Save CI resources** - Don't waste build minutes
- ✅ **Easier debugging** - Direct container access
- ✅ **Catch issues early** - Before they hit version control
## Common Commands
### Build and Run
```bash
# Start everything (builds if needed)
docker-compose -f docker-compose.prod-test.yml up
# Build with no cache (clean build)
docker-compose -f docker-compose.prod-test.yml build --no-cache
# Run in background
docker-compose -f docker-compose.prod-test.yml up -d
# View logs
docker-compose -f docker-compose.prod-test.yml logs -f app
```
### Debug Inside Container
```bash
# Execute shell in running container
docker-compose -f docker-compose.prod-test.yml exec app /bin/sh
# Check supervisor status
docker-compose -f docker-compose.prod-test.yml exec app supervisorctl status
# View nginx logs
docker-compose -f docker-compose.prod-test.yml exec app cat /var/log/nginx/error.log
# View Laravel logs
docker-compose -f docker-compose.prod-test.yml exec app tail -f /var/www/html/storage/logs/laravel.log
```
### Cleanup
```bash
# Stop everything
docker-compose -f docker-compose.prod-test.yml down
# Remove volumes too (fresh database)
docker-compose -f docker-compose.prod-test.yml down -v
```
## Testing Workflow
### Before Pushing to CI/CD:
1. **Make your changes** to Dockerfile, configs, etc.
2. **Test locally:**
```bash
docker-compose -f docker-compose.prod-test.yml up --build
```
3. **Verify it works:**
- App accessible at http://localhost:8080
- No errors in logs: `docker-compose -f docker-compose.prod-test.yml logs app`
- All services running: `docker-compose -f docker-compose.prod-test.yml exec app supervisorctl status`
4. **If it works, push to develop:**
```bash
git add .
git commit -m "fix: your change"
git push origin develop
```
5. **If it doesn't work, debug:**
```bash
# Check logs
docker-compose -f docker-compose.prod-test.yml logs app
# Exec into container
docker-compose -f docker-compose.prod-test.yml exec app /bin/sh
# Make fixes and rebuild
docker-compose -f docker-compose.prod-test.yml up --build
```
## Differences from Production
This local test environment differs from production K8s in:
- Uses local PostgreSQL (not persistent volume)
- Runs on localhost:8080 (not ingress with TLS)
- Uses test APP_KEY (not secret from K8s)
- Single replica (not multiple pods)
- No load balancer or ingress
But it **does test**:
- ✅ Dockerfile builds correctly
- ✅ All directories exist and have correct permissions
- ✅ Supervisor starts all services (nginx, php-fpm, workers, scheduler)
- ✅ Laravel boots and connects to database
- ✅ Migrations run successfully
- ✅ Application responds to HTTP requests
## Troubleshooting
### Port conflicts
If 8080 or 5433 are already in use, edit `docker-compose.prod-test.yml` and change the ports.
### Build errors
```bash
# Clean build from scratch
docker-compose -f docker-compose.prod-test.yml build --no-cache --pull
```
### Container crashes immediately
```bash
# Check logs for error
docker-compose -f docker-compose.prod-test.yml logs app
# Try running without detach to see output
docker-compose -f docker-compose.prod-test.yml up
```
### Need fresh database
```bash
# Remove volumes and recreate
docker-compose -f docker-compose.prod-test.yml down -v
docker-compose -f docker-compose.prod-test.yml up
```
## Integration with Development Workflow
### Using alongside Laravel Sail
Sail and prod-test can run simultaneously:
- Sail dev environment: http://localhost (port 80)
- Production test: http://localhost:8080
Just make sure to use different database ports (Sail: 5432, prod-test: 5433).
### When to use each:
**Laravel Sail (docker-compose.yml):**
- Daily development
- Running artisan commands
- Testing code changes
- Hot reload with Vite
**Production Test (docker-compose.prod-test.yml):**
- Before pushing to develop
- Testing Dockerfile changes
- Testing supervisor/nginx configs
- Verifying production build process
## Next Steps After Local Success
Once your image works locally:
1. ✅ Commit and push to develop
2. ✅ CI/CD builds the same image
3. ✅ Deploy to Kubernetes with confidence
4. ✅ Much faster iteration cycle!

View File

@@ -0,0 +1,75 @@
# Docker Compose for testing production image locally
# Usage: docker-compose -f docker-compose.prod-test.yml up --build
version: '3.8'
services:
# PostgreSQL database for local testing
postgres:
image: postgres:17
environment:
POSTGRES_DB: cannabrands_test
POSTGRES_USER: cannabrands_test
POSTGRES_PASSWORD: test_password
ports:
- "5433:5432" # Different port to avoid conflicts with Sail
volumes:
- postgres_test_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U cannabrands_test"]
interval: 5s
timeout: 5s
retries: 5
# Your production Laravel app
app:
build:
context: .
dockerfile: Dockerfile
args:
GIT_COMMIT_SHA: local-test
APP_VERSION: local
ports:
- "8080:80" # Map to 8080 to avoid conflicts with Sail
environment:
# Application
APP_NAME: "Cannabrands CRM (Test)"
APP_ENV: local
APP_DEBUG: "true"
APP_KEY: "base64:zYloN4cn3qDHbL6h2VrP32M7+/D582YVrtMT25DR+ik=" # Test key
APP_URL: http://localhost:8080
# Database
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: cannabrands_test
DB_USERNAME: cannabrands_test
DB_PASSWORD: test_password
# Cache & Sessions
CACHE_DRIVER: file
SESSION_DRIVER: database
QUEUE_CONNECTION: database
VIEW_COMPILED_PATH: /var/www/html/storage/framework/views
# Mail (use log driver for testing)
MAIL_MAILER: log
MAIL_FROM_ADDRESS: test@cannabrands.local
MAIL_FROM_NAME: "Cannabrands CRM Test"
# Logging
LOG_CHANNEL: stack
LOG_LEVEL: debug
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
volumes:
postgres_test_data:
driver: local