
Docker Local Dev
- 77 installs
- 62 repo stars
- Updated August 5, 2026
- thienanblog/awesome-ai-agent-skills
Helps with devops & ci/cd tasks.
About
docker-local-dev is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- docker-local-dev
- DevOps & CI/CD
- AI-coding skill
Docker Local Dev by the numbers
- 77 all-time installs (skills.sh)
- +5 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #601 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thienanblog/awesome-ai-agent-skills --skill docker-local-devAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 77 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 5, 2026 |
| Repository | thienanblog/awesome-ai-agent-skills ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
Docker Local Development Environment Generator
Overview
This skill helps you create optimized Docker development environments for your projects. It generates docker-compose.yml, Dockerfile, and related configurations through an interactive, question-driven workflow.
When to use this skill:
- Setting up a new Docker development environment
- Dockerizing an existing project for local development
- Adding services (database, Redis, email testing) to your Docker setup
- Updating or merging with existing Docker configurations
Key Principle: This skill ALWAYS asks questions before making decisions. You will be notified about each configuration choice and can adjust settings to match your exact needs.
Important Notice
This skill uses an interactive approach. Before generating any files, I will:
1. Run auto-detection scripts to identify your tech stack (saves AI tokens) 2. Present the detection results for your confirmation 3. Ask 10-15 clarifying questions about your preferences 4. Show you a preview before creating or modifying files
Why this approach? Docker configurations are project-specific. Asking questions ensures the setup matches YOUR requirements, not generic defaults. This prevents issues and saves debugging time later.
Core Design Defaults
- Treat local development and production as separate targets. Local prioritizes live reload, bind mounts, fast debug/test cycles, and optional debug tooling. Production prioritizes immutable images, small runtime layers, no bind mounts, no dev dependency installers, and scoped runtime secrets.
- Prefer bind-mounted source plus named dependency volumes for active development. For PHP/Node monorepos, one-shot dependency installer services are valid and intentional; they install
vendor,node_modules, or package-manager stores into named volumes, exit with code 0, and may appear as stopped in Docker UIs. - Use dependency installer services only for local/dev compose unless the user explicitly wants a production-like dev image. Production images should install dependencies during image build.
- Do not assume wildcard local domains. Prefer explicit
*.localhosthostnames because host machines commonly resolve them to127.0.0.1without/etc/hostschanges. Ask whether wildcard routing is needed locally only when the user is actively testing wildcard behavior. If the user only needs production wildcard support, use explicit local hostnames and document production wildcard requirements separately. - When frontend apps use same-origin
/apiproxying to the API, preserve that route through the local reverse proxy instead of introducing browser CORS/preflight requirements. - Generalize examples for community-safe reuse. Use neutral local placeholders such as
app.localhost,api.localhost,apps/web, andpackages/ui; never copy private project names, customer names, private domains, internal paths, secrets, or production data into reusable skill content. - For implementation details, read the relevant reference before generating files: service strategy in
references/service-configuration-guide.md, domain/networking inreferences/networking-ports-guide.md, and verification inreferences/health-check-patterns.md. - For host port tracking, use a per-user registry path, never a machine-specific project path. Resolve it as
${DOCKER_LOCAL_DEV_PORT_REGISTRY}when set; otherwise use${XDG_STATE_HOME:-$HOME/.local/state}/docker-local-dev/HOST_PORT_REGISTRY.md. Read an existing registry before proposing host-exposed ports. Before creating or updating it, explain the exact path and scan root, then ask for confirmation because the registry may record local project names, service names, and exposed ports.
Quick Start
To generate a Docker development environment: 1. Navigate to your project root 2. Tell the AI: "Use the docker-local-dev skill to set up Docker" 3. Confirm or correct the auto-detected tech stack 4. Answer the configuration questions 5. Review and approve the generated files
Supported Tech Stacks
| Stack | Framework/CMS | Process Manager | Notes |
|---|---|---|---|
| PHP | Laravel 10/11/12/13 | Supervisor | Queue workers, scheduler |
| PHP | WordPress | WP-CLI | Debug plugins, error logging |
| PHP | Drupal 10/11 | Drush | Development services |
| PHP | Joomla 4/5 | - | CLI tools, debug mode |
| Node.js | Express, NestJS, Next.js | PM2 or Supervisor | Hot reload support |
| Python | Django, FastAPI, Flask | Celery, Supervisor | WSGI/ASGI servers |
Unsupported Stack? The skill will proceed with generic configuration and suggest contributing improvements. See CONTRIBUTING.md for details.
Interactive Workflow
Phase 0: Auto-Detection (Script-based)
Before using AI, run detection scripts to save tokens:
# The skill will run this automatically
./scripts/detect-stack.shDetection checks:
composer.json→ Laravel, PHP versionwp-config.php,wp-content/→ WordPresscore/,sites/default/→ Drupalconfiguration.php,administrator/→ Joomlapackage.json→ Node.js, framework, versionrequirements.txt,pyproject.toml→ Python, framework.env, config files → Database type, Redis usage
Present results to user:
I detected: Laravel 11 + PHP 8.3 + MySQL + Redis
Is this correct?
- Yes → proceed with detected settings
- No → I'll analyze further using AIIf stack is NOT officially supported:
I detected [stack] but this is not in our supported list.
The Docker setup may not be optimal.
Proceeding with generic configuration...
If this works for you, please consider contributing to improve support!
See: CONTRIBUTING.mdPhase 1: Initial Discovery
Check for existing Docker files:
1. Look for docker-compose.yml, docker-compose.yaml, Dockerfile 2. If found, ask:
I found existing Docker files:
- docker-compose.yml (modified 2 days ago)
- Dockerfile
How should I proceed?
1. Merge (preserve your custom settings, add new services)
2. Replace (backup existing, generate fresh)
3. Cancel (let me review first)Backup strategy:
- Timestamped backups:
docker-compose.yml.backup.2024-01-15-143022 - Never overwrite without backup
Phase 1.5: Naming Strategy
IMPORTANT: Ask about Docker naming before generating Compose files.
Container UIs such as OrbStack and Docker Desktop do not only show the container name. They also group containers by the Docker Compose project name and list child rows by service name. In monorepos or on machines with many stacks, generic names like web, app, or websocket become hard to scan.
Always ask:
How would you like this stack named in Docker UIs?
1. Project-prefixed names (recommended)
- Compose project/group: `inventory-office-web`
- Container name: `inventory-office-web`
- Service names: explicit when helpful, otherwise role-based inside the project
2. Minimal names
- Compose project/group: folder name
- Container name: default Compose-generated name
- Service names: short generic names like `web`, `app`, `db`Recommended defaults for monorepos or multi-project machines:
- Set the top-level Compose
name:field to an explicit project slug such asinventory-api,inventory-office-web, orinventory-websocket - Set
container_name:to the same explicit prefix pattern, for exampleinventory-api-app,inventory-office-web,inventory-websocket - Prefer explicit single-service names when the whole stack is one app/service, for example
office-web:orwebsocket: - For multi-service stacks, role-based service names are acceptable under a clear project/group name, for example
app,web,db,redisinsideinventory-api
Rules:
- Never rely on the folder name alone for Compose grouping in monorepos
- Prefer kebab-case names
- Keep the same prefix across project name, image tags, and container names when possible
- If an existing stack already has a stable naming convention, preserve it unless the user asks to rename it
Phase 1.75: Monorepo Discovery
Detect monorepo/workspace layout before generating services:
find . -maxdepth 3 \( -name pnpm-workspace.yaml -o -name turbo.json -o -name nx.json -o -name lerna.json -o -name package.json -o -name composer.json \) -print
find . -maxdepth 2 -type d \( -name apps -o -name packages -o -name services \) -printAsk monorepo-specific questions:
This looks like a monorepo/workspace.
Which apps should run in Docker?
- App path and role, for example apps/api, apps/web, services/worker
- Dev command for each app
- Internal port for each app
- Public local hostname, if any
- Shared packages that must live-reload
- Package manager and lockfile locationRecommended monorepo defaults:
- Use the repository root as
build.contextwhen Dockerfiles need shared packages or root lockfiles. - Set app-specific
dockerfile,working_dir, command, and dependency volumes per service. - Mount the repo root only when workspace resolution or shared package live reload requires it; otherwise mount app paths narrowly.
- Keep top-level Compose
name:explicit and service names role-based under that project. - Do not leak real project names/domains in reusable examples; use neutral names such as
api,web,admin,worker,app.localhost, andapps/*.
Phase 2: Tech Stack Confirmation
If auto-detection succeeded:
Detected configuration:
- Framework: Laravel 11
- PHP Version: 8.3
- Database: MySQL (from .env DB_CONNECTION)
- Redis: Yes (from .env REDIS_HOST)
- Queue: Yes (jobs table detected)
Please confirm or adjust these settings.If auto-detection failed or unclear:
What is your primary tech stack?
1. PHP/Laravel
2. WordPress
3. Drupal
4. Joomla
5. Node.js (Express/NestJS/Next.js)
6. Python (Django/FastAPI/Flask)
7. Other (I'll try generic configuration)Phase 3: CMS-Specific Questions
WordPress:
WordPress Development Options:
1. Install debug plugins?
- Query Monitor (SQL queries, hooks, conditionals)
- Debug Bar (debug info in admin bar)
2. Enable WP_DEBUG and error logging?
- WP_DEBUG = true
- WP_DEBUG_LOG = true
- SCRIPT_DEBUG = trueDrupal:
Drupal Development Options:
1. Install Drush globally in container?
2. Enable development services (verbose errors, twig debug)?
3. Disable caching for development?Joomla:
Joomla Development Options:
1. Enable debug mode?
2. Install Joomla CLI tools?Phase 3.5: Existing Docker Images Scan
Before suggesting service versions, check locally available images to save disk space:
# The skill will run this automatically
./scripts/detect-images.shPresent results to user:
I found these images already on your machine:
Databases:
- mysql:8.0.35 (2.3 GB)
- mariadb:11.2 (1.1 GB)
Using existing images saves disk space and download time.
Which database would you like to use?
1. mysql:8.0.35 (already downloaded - saves 2.3 GB)
2. mariadb:11.2 (already downloaded - saves 1.1 GB)
3. Different version (will download new image)
→ What version do you need for production compatibility?If no existing images found:
No database images found locally.
Which database would you like to use?
1. MySQL 8.0 (recommended for Laravel/WordPress)
2. MariaDB 11 (MySQL-compatible, smaller)
3. PostgreSQL 16 (if your app requires it)Same approach for other services:
- Check for existing Redis, PHP, Node, Nginx, Mailpit/MailHog images
- Suggest matching versions when available
- Always offer "different version" option for production compatibility
Phase 4: Service Configuration (Smart Recommendations)
IMPORTANT: Check actual usage before recommending services.
Before suggesting any optional service, verify if it's actually being used in the project:
# Check .env for actual service usage
grep -E '^(CACHE_DRIVER|CACHE_STORE|SESSION_DRIVER|QUEUE_CONNECTION|MAIL_MAILER)=' .envDatabase Selection (always needed, use images from Phase 3.5):
Which database would you like to use?
1. MySQL 8.0 (recommended for Laravel/WordPress)
2. MariaDB 11 (MySQL-compatible, lighter)
3. PostgreSQL 16 (required for some apps)Redis Configuration (check actual usage first):
First, check if Redis is actually used:
grep -E '^(CACHE_DRIVER|SESSION_DRIVER|QUEUE_CONNECTION)=' .envIf Redis is NOT in use (CACHE_DRIVER=file, SESSION_DRIVER=file, QUEUE_CONNECTION=sync):
I noticed your .env configuration:
- CACHE_DRIVER=file (not using Redis for cache)
- SESSION_DRIVER=file (not using Redis for sessions)
- QUEUE_CONNECTION=sync (not using Redis for queues)
Redis is not currently used in your project.
Do you want to add Redis anyway?
1. No, skip Redis (recommended based on your config)
2. Yes, I plan to switch to Redis laterIf Redis IS in use (any of the above = redis):
Do you need Redis?
1. Yes, for caching only
2. Yes, for caching + sessions
3. Yes, for caching + sessions + queues
4. No, I don't need RedisEmail Testing (check actual usage first):
First, check MAIL_MAILER setting:
grep -E '^MAIL_MAILER=' .envIf using log or array mailer:
Your MAIL_MAILER is set to 'log' (emails logged, not sent).
Do you want to add email testing service anyway?
1. No, skip email testing (recommended based on your config)
2. Yes, add Mailpit for testingIf using smtp or other mailer:
Which email testing service would you prefer?
1. Mailpit (modern, actively maintained, recommended)
- Web UI: http://localhost:8025
- SMTP: localhost:1025
2. MailHog (widely used, stable)
- Web UI: http://localhost:8025
- SMTP: localhost:1025
3. None (I'll configure email separately)Background Task Processing (check actual usage first):
For Laravel, check if queues are actually used:
grep -E '^QUEUE_CONNECTION=' .env
# Also check if async jobs, failed jobs, queued listeners, or dispatch calls exist
find app -path '*/Jobs/*' -type f 2>/dev/null
grep -R "ShouldQueue\|queue:work\|dispatch(" app routes config 2>/dev/null
# If dependencies are installed, inspect scheduler tasks
php artisan schedule:list --no-interaction 2>/dev/null || trueIf QUEUE_CONNECTION=sync:
Your QUEUE_CONNECTION is set to 'sync' (no background processing).
Do you need background task processing anyway?
1. No, skip queue workers (recommended based on your config)
2. Yes, I plan to switch to async queues laterIf QUEUE_CONNECTION=database/redis:
Do you need background task processing?
1. Queue workers only (Supervisor)
2. Scheduler only (cron replacement via Supervisor)
3. Both queue workers and scheduler
4. No background processing neededLaravel scheduler rule:
- A scheduler service is infrastructure support, not proof that scheduled tasks exist.
- If
schedule:listshows no tasks, ask whether to add a scheduler service now or document it as production-ready but idle. - In production compose, run queue workers and the scheduler as separate services from the web/API container.
For Node.js:
How do you want to manage Node.js processes?
1. PM2 (process manager with clustering, recommended)
2. Supervisor (simple process monitoring)
3. Direct node command (development only)For Python:
Background task processing options:
1. Celery workers (for Django/FastAPI async tasks)
2. Supervisor for scheduled tasks (cron replacement)
3. Both Celery and scheduled tasks
4. No background processing neededPhase 5: Port Exposure & Configuration
IMPORTANT: Ask about reverse proxy first before exposing ports.
Host Port Registry Preflight:
Before asking for or suggesting any host-exposed port, resolve and check the user's host port registry:
PORT_REGISTRY_FILE="${DOCKER_LOCAL_DEV_PORT_REGISTRY:-${XDG_STATE_HOME:-$HOME/.local/state}/docker-local-dev/HOST_PORT_REGISTRY.md}"
test -f "$PORT_REGISTRY_FILE" && sed -n '1,220p' "$PORT_REGISTRY_FILE"Use the Suggested Free Ports, Conflicts And Shared Ports, Configured Ports, and Runtime Listeners sections to avoid reusing ports already assigned to Docker Compose stacks, Vite dev servers, Webpack dev servers, Next/Nuxt dev servers, local databases, Redis, Mailpit/MailHog, reverse proxies, or running host processes.
If the registry is missing or stale, do not create or update it silently. Ask first:
I did not find a current host port registry at:
$PORT_REGISTRY_FILE
Do you want me to create/update it by scanning this root?
<absolute scan root>
The registry may include local project names, service names, file paths, and host-exposed ports.Only after the user confirms, run the scanner from this skill directory:
node ./scripts/scan-host-ports.mjs --root "<absolute scan root>" --out "$PORT_REGISTRY_FILE" --yesAsk which scan root to use when it is not obvious. Use the current project root only after confirming that the user wants a project-scoped registry scan. Do not assume any private projects folder.
Reverse Proxy Check:
Are you using a reverse proxy (Nginx Proxy Manager, Traefik, Caddy)?
1. Yes, I'm using a reverse proxy
→ Ports will remain internal only
→ Services communicate via Docker network
→ You'll configure the proxy to route to containers
2. No, I want to expose ports directly
→ I'll help you choose which ports to exposeIf using reverse proxy (Option 1):
Since you're using a reverse proxy, ports will be internal only.
Do you still want to expose the database port for external tools (DBeaver/DataGrip)?
1. Yes, expose database port (3306/5432) for SQL tools
2. No, keep everything internalLocal domain strategy:
Which local domains should this stack support?
Examples:
- admin.localhost
- app.localhost
- renderer.localhost
Do you need wildcard domains locally?
1. No, use explicit `*.localhost` hosts only (recommended unless actively testing wildcard routing)
2. Yes, configure wildcard local DNS/proxy routing
3. Production wildcard only, keep local explicitIf explicit and wildcard hosts are both used, configure specific hosts before wildcard routes. For host-machine access, prefer *.localhost names so the user does not need to edit /etc/hosts. Reserve apex/company domains that the user says are not implemented yet; do not route them to placeholder services unless asked.
If NOT using reverse proxy (Option 2), then ask port strategy:
How do you want to expose ports?
1. Minimal (recommended for most projects)
- Nginx: 8080 (web access)
- Database: 3306/5432 (for SQL tools like DBeaver/DataGrip)
2. Full exposure (all services accessible)
- Nginx: 8080
- Database: 3306/5432
- Redis: 6379
- Mail UI: 8025
- PHP-FPM: 9000 (if needed)Port Availability Check:
Checking port availability...
Port 8080: Available
Port 3306: IN USE (another MySQL instance)
→ Suggesting 3307 instead
Port 6379: AvailableWhen reporting availability, include both the live socket check (lsof, nc, or docker ps) and the registry check. A port listed in the resolved host port registry should be treated as reserved even when it is not currently listening.
Configuration Storage:
Where do you want to store configuration?
1. .env file (recommended)
- Easier to change ports and settings
- Keep secrets out of docker-compose.yml
- Example: APP_PORT=8080, DB_PORT=3306
2. Directly in docker-compose.yml
- Simpler for basic setups
- All config in one place
- Less flexible for different environmentsPhase 6: Network Configuration
First, scan for existing Docker networks and reverse proxies:
# The skill will run this automatically
./scripts/detect-network.shIf reverse proxy container detected (Nginx Proxy Manager, Traefik, Caddy):
I scanned your Docker environment:
Reverse proxy found:
- Container: 'nginx-proxy-manager' on network: 'npm_default'
Do you want to connect this project to 'npm_default'?
1. Yes, use 'npm_default' for reverse proxy routing (recommended)
→ No port exposure needed
→ Configure routing in your proxy dashboard
2. No, use isolated network
→ I'll ask about port exposureIf NO reverse proxy detected:
No reverse proxy containers detected (Nginx Proxy Manager, Traefik, Caddy).
Available Docker networks:
- myapp_default
- shared_services
Do you want to:
1. Create isolated network for this project (recommended)
2. Use existing network: [select from list]
3. Create shared network for multiple projectsMultiple Projects (if no proxy detected):
Are you running multiple Docker projects on this machine?
1. Yes, I have multiple projects
→ Consider Nginx Proxy Manager for:
- Custom domains (app.localhost, api.localhost)
- Automatic SSL certificates
- Centralized reverse proxy
2. No, this is my only Docker project
→ Use isolated project networkMicroservices/API Connection:
Does this project need to connect to other Docker services?
1. Yes, I have other Docker services (APIs, microservices)
→ Create external shared network
→ Services can communicate via container names
2. No, this project is standalone
→ Use project-isolated networkPhase 7: Volume Mount Strategy
Always explain options:
How would you like to mount your source code?
1. Bind mount (recommended for development)
- Your local files sync to container immediately
- Changes reflect instantly without rebuild
- Best for: Active development, hot reload
2. Named volume (better performance)
- Files copied into Docker volume
- Faster file operations (especially on macOS)
- Requires rebuild to see code changes
- Best for: Testing, CI/CD
Note: Bind mounts have ~10-20% slower file I/O on macOS,
but the instant sync is worth it for development.Dependency installation strategy:
How should dependencies be handled for local development?
1. One-shot dependency installer services (recommended for bind-mounted live reload)
- Install Composer/npm/pnpm dependencies into named Docker volumes
- App containers can start after dependency services complete successfully
- Installer containers exit 0 and will show as stopped; this is expected
2. Install dependencies inside the dev image
- Faster after image build
- Requires rebuilding when lockfiles change
- Better for production-like dev, less flexible for active local editsFor production builds, install dependencies in image build stages and do not generate dependency installer services.
Phase 8: Generation & Verification
Docker Compose Version Note:
- Do NOT include
version:at the top of docker-compose.yml - Docker Compose v2 deprecated this field
- Modern compose files don't need it
Compose naming note:
- Prefer setting top-level
name:explicitly so Docker UIs show a stable, searchable group name - Do not leave Compose project naming to the working directory when generating configs for monorepos
- If
container_name:is used, keep it aligned with the project/group naming convention chosen in Phase 1.5
File generation order: 1. Create backup of existing files (if any) 2. Generate .env.docker or update .env 3. Generate Dockerfile 4. Generate .dockerignore or update it to exclude secrets and build artifacts 5. Generate docker-compose.yml (without version field, with explicit top-level name: when requested or when working in a monorepo) 6. Add one-shot dependency installer services only for local/dev compose when chosen 7. Keep production compose separate from dev compose when the user asks for both 8. Generate Nginx/reverse proxy configuration 9. Generate Supervisor/PM2 configuration (if needed) 10. Create helper scripts 11. If host-exposed Docker ports or dev-server ports changed, ask before updating the resolved host port registry. After confirmation, refresh it with node ./scripts/scan-host-ports.mjs --root "<absolute scan root>" --out "$PORT_REGISTRY_FILE" --yes.
Production boundary:
- Do not load app-local
.envfiles into production compose. - Prefer a root production env file or deployment secrets mechanism and scope env vars to only the services that need them.
- Do not pass database, SMTP, S3, or app secrets to frontend/static/proxy containers unless they genuinely need them.
- For Caddy/Traefik wildcard HTTPS, document DNS-01 or preloaded wildcard certificate requirements.
After docker-compose up (AUTOMATIC):
# These run automatically after containers start
# 1. Verify all ports are available
./scripts/port-check.sh
# 2. Health check all services
./scripts/health-check.sh
# 3. Test database with simple CRUD
./scripts/db-test.sh
# 4. Generate usage documentation
# Creates USAGE.md with commands for your stackHealth Check Output:
Docker Local Dev - Health Check
================================
Checking Nginx.............. OK
Checking PHP-FPM............ OK
Checking MySQL.............. OK
Checking Redis.............. OK
Checking Mailpit............ OK
Checking Queue Worker....... OK
Database CRUD Test:
- CREATE table............ OK
- INSERT data............. OK
- UPDATE data............. OK
- DELETE data............. OK
- DROP table.............. OK
All services are healthy!
Your development environment is ready:
- Web: http://localhost:8080
- Database: localhost:3306 (user: root, pass: secret)
- Mail UI: http://localhost:8025Phase 9: Documentation Generation
Automatically creates USAGE.md:
# Docker Development Environment
## Quick Commands
Start containers:
docker compose up -d
Stop containers:
docker compose down
View logs:
docker compose logs -f
## Accessing Services
| Service | URL/Host | Credentials |
|---------|----------|-------------|
| Web | http://localhost:8080 | - |
| Database | localhost:3306 | root / secret |
| Redis | localhost:6379 | - |
| Mail UI | http://localhost:8025 | - |
## Container Networking (Important)
When the app runs inside Docker, `localhost` points to the app container. To connect to other services, use the Docker Compose service name (for example `db`, `redis`, `mailpit`) instead of `localhost`.
## Dependency Installer Containers
If this stack uses dependency installer services such as `api-deps` or `node-deps`, they are expected to exit successfully after installing dependencies into named volumes. In Docker Desktop/OrbStack they may appear as stopped. Check the exit code and logs before treating them as failed:
docker compose ps -a
docker compose logs api-deps node-deps
## Stack-Specific Commands
### Laravel
docker compose exec app php artisan migrate
docker compose exec app php artisan queue:work
### WordPress
docker compose exec app wp plugin list
docker compose exec app wp cache flushMerge Strategy
When merging with existing Docker files:
1. Preserve user customizations:
- Custom environment variables
- Volume mounts
- Network configurations
- Port mappings
- Existing Compose project naming (
name:), service naming, and container naming unless the user explicitly asks to normalize them
2. Add new services:
- Only add services that don't exist
- Don't modify existing service definitions
3. Show diff before applying:
+ redis:
+ image: redis:alpine
+ volumes:
+ - redis_data:/data
services:
app:
# existing config preserved4. Require confirmation:
These changes will be applied:
- Add Redis service
- Add redis_data volume
- Update app service to depend on Redis
Proceed? [y/N]Health Check Protocol
Services are verified in this order:
1. Database (MySQL/PostgreSQL)
- Connection test:
mysqladmin pingorpg_isready - CRUD test: CREATE/INSERT/UPDATE/DELETE on test table
2. Web Server (Nginx)
- HTTP request to localhost
- Expect 200, 301, or 302 response
3. Application
- Stack-specific checks
- Laravel:
php artisan about - WordPress:
wp core version - Django:
python manage.py check
4. Redis (if enabled)
redis-cli ping→ expect PONG
5. Queue Worker (if enabled)
- Process verification
- Test job processing (optional)
6. Email Service (if enabled)
- SMTP connection test
- Web UI accessibility
Troubleshooting
Port Already in Use
Error: Port 3306 is already in use
Solutions:
1. Stop the conflicting service
2. Use a different port (skill will suggest alternatives)
3. Check: lsof -i :3306Database Connection Failed
Error: Cannot connect to MySQL
Check:
1. Is the container running? docker compose ps
2. Is the port exposed? docker compose port db 3306
3. Are credentials correct? Check .env or docker-compose.ymlPermission Denied
Error: Permission denied on mounted volume
Solutions:
1. Check file ownership: ls -la
2. Match container user ID: Add user: "1000:1000" to service
3. Use :cached or :delegated mount options on macOSReference Documentation
- Tech Stack Detection
- Service Configuration Guide
- CMS Configuration Guide
- Networking & Ports Guide
- Merge & Backup Strategy
- Health Check Patterns
Contributing
If your tech stack is not fully supported, please consider contributing!
See CONTRIBUTING.md for:
- How to add support for new tech stacks
- Template structure requirements
- Testing guidelines
# Base Docker Compose Template
# Template markers: {{PROJECT_NAME}}, {{NETWORK_NAME}}
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
# Services will be added based on detected stack
networks:
{{NETWORK_NAME}}:
driver: bridge
volumes:
db_data:
redis_data:
# Node.js Docker Compose Template
# Generated by docker-local-dev skill
# Supports: Express, NestJS, Fastify, Next.js
# Template variables: {{PROJECT_NAME}}, {{NODE_VERSION}}, {{DB_IMAGE}}, etc.
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
# Node.js Application
app:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_app
restart: unless-stopped
working_dir: /app
volumes:
- ./:/app
- /app/node_modules # Named volume for node_modules (performance)
environment:
NODE_ENV: development
PORT: 3000
DB_HOST: db
DB_PORT: {{DB_PORT_INTERNAL}}
DB_NAME: {{DB_DATABASE}}
DB_USER: {{DB_USER}}
DB_PASSWORD: {{DB_PASSWORD}}
REDIS_HOST: redis
REDIS_PORT: 6379
ports:
- "${APP_PORT:-3000}:3000"
depends_on:
db:
condition: service_healthy
networks:
- {{PROJECT_NAME}}_network
# For development with hot reload:
command: npm run dev
# For PM2 in production-like mode:
# command: pm2-runtime start ecosystem.config.js
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
# Nginx Reverse Proxy (Optional - for production-like setup)
nginx:
image: nginx:alpine
container_name: {{PROJECT_NAME}}_nginx
restart: unless-stopped
ports:
- "${NGINX_PORT:-8080}:80"
- "${NGINX_SSL_PORT:-8443}:443"
volumes:
- ./docker/nginx/node-proxy.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
- ./public:/var/www/public:ro # Static files
depends_on:
- app
networks:
- {{PROJECT_NAME}}_network
# Database Server (MySQL/MariaDB)
db:
image: {{DB_IMAGE}}
container_name: {{PROJECT_NAME}}_db
restart: unless-stopped
ports:
- "${DB_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: {{DB_ROOT_PASSWORD}}
MYSQL_DATABASE: {{DB_DATABASE}}
MYSQL_USER: {{DB_USER}}
MYSQL_PASSWORD: {{DB_PASSWORD}}
volumes:
- db_data:/var/lib/mysql
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{{DB_ROOT_PASSWORD}}"]
interval: 10s
timeout: 5s
retries: 5
# PostgreSQL Alternative (uncomment if using PostgreSQL)
# db:
# image: postgres:16-alpine
# container_name: {{PROJECT_NAME}}_db
# restart: unless-stopped
# ports:
# - "${DB_PORT:-5432}:5432"
# environment:
# POSTGRES_DB: {{DB_DATABASE}}
# POSTGRES_USER: {{DB_USER}}
# POSTGRES_PASSWORD: {{DB_PASSWORD}}
# volumes:
# - db_data:/var/lib/postgresql/data
# networks:
# - {{PROJECT_NAME}}_network
# healthcheck:
# test: ["CMD-SHELL", "pg_isready -U {{DB_USER}} -d {{DB_DATABASE}}"]
# interval: 10s
# timeout: 5s
# retries: 5
# Redis Cache
redis:
image: redis:7-alpine
container_name: {{PROJECT_NAME}}_redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Email Testing
mailpit:
image: axllent/mailpit:latest
container_name: {{PROJECT_NAME}}_mailpit
restart: unless-stopped
ports:
- "${MAIL_UI_PORT:-8025}:8025"
- "${MAIL_SMTP_PORT:-1025}:1025"
networks:
- {{PROJECT_NAME}}_network
# Cron/Scheduler via Supervisor (Optional)
# scheduler:
# build:
# context: .
# dockerfile: Dockerfile
# container_name: {{PROJECT_NAME}}_scheduler
# restart: unless-stopped
# working_dir: /app
# volumes:
# - ./:/app
# - /app/node_modules
# - ./docker/supervisor/node-cron.conf:/etc/supervisor/conf.d/cron.conf:ro
# environment:
# NODE_ENV: development
# depends_on:
# - db
# - redis
# networks:
# - {{PROJECT_NAME}}_network
# command: ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]
networks:
{{PROJECT_NAME}}_network:
driver: bridge
# For multi-project or microservices:
# external: true
# name: shared_network
volumes:
db_data:
driver: local
redis_data:
driver: local
# Drupal Docker Compose Template
# Generated by docker-local-dev skill
# Template variables: {{PROJECT_NAME}}, {{PHP_VERSION}}, {{DB_IMAGE}}, {{DB_PORT}}, etc.
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
# Drupal Application with PHP-FPM
app:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_app
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./:/var/www/html
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini:ro
environment:
DRUPAL_DB_HOST: db
DRUPAL_DB_PORT: 3306
DRUPAL_DB_NAME: {{DB_DATABASE}}
DRUPAL_DB_USER: {{DB_USER}}
DRUPAL_DB_PASSWORD: {{DB_PASSWORD}}
depends_on:
db:
condition: service_healthy
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
interval: 10s
timeout: 5s
retries: 3
# Nginx Web Server
nginx:
image: nginx:alpine
container_name: {{PROJECT_NAME}}_nginx
restart: unless-stopped
ports:
- "${APP_PORT:-8080}:80"
- "${APP_SSL_PORT:-8443}:443"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/drupal.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- {{PROJECT_NAME}}_network
# Database Server (MySQL/MariaDB or PostgreSQL)
db:
image: {{DB_IMAGE}}
container_name: {{PROJECT_NAME}}_db
restart: unless-stopped
ports:
- "${DB_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: {{DB_ROOT_PASSWORD}}
MYSQL_DATABASE: {{DB_DATABASE}}
MYSQL_USER: {{DB_USER}}
MYSQL_PASSWORD: {{DB_PASSWORD}}
volumes:
- db_data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{{DB_ROOT_PASSWORD}}"]
interval: 10s
timeout: 5s
retries: 5
# Drush Container (for running Drupal commands)
drush:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_drush
working_dir: /var/www/html
volumes:
- ./:/var/www/html
depends_on:
- db
environment:
DRUPAL_DB_HOST: db
DRUPAL_DB_PORT: 3306
DRUPAL_DB_NAME: {{DB_DATABASE}}
DRUPAL_DB_USER: {{DB_USER}}
DRUPAL_DB_PASSWORD: {{DB_PASSWORD}}
networks:
- {{PROJECT_NAME}}_network
entrypoint: ["./vendor/bin/drush"]
# Usage: docker compose run --rm drush <command>
# Example: docker compose run --rm drush status
# Redis Cache (Optional - uncomment if needed)
# redis:
# image: redis:7-alpine
# container_name: {{PROJECT_NAME}}_redis
# restart: unless-stopped
# ports:
# - "${REDIS_PORT:-6379}:6379"
# volumes:
# - redis_data:/data
# networks:
# - {{PROJECT_NAME}}_network
# healthcheck:
# test: ["CMD", "redis-cli", "ping"]
# interval: 10s
# timeout: 5s
# retries: 3
# Email Testing
mailpit:
image: axllent/mailpit:latest
container_name: {{PROJECT_NAME}}_mailpit
restart: unless-stopped
ports:
- "${MAIL_UI_PORT:-8025}:8025"
- "${MAIL_SMTP_PORT:-1025}:1025"
networks:
- {{PROJECT_NAME}}_network
networks:
{{PROJECT_NAME}}_network:
driver: bridge
volumes:
db_data:
driver: local
# redis_data:
# driver: local
# Joomla Docker Compose Template
# Generated by docker-local-dev skill
# Template variables: {{PROJECT_NAME}}, {{PHP_VERSION}}, {{DB_IMAGE}}, {{DB_PORT}}, etc.
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
# Joomla Application with PHP-FPM
app:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_app
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./:/var/www/html
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini:ro
environment:
JOOMLA_DB_HOST: db
JOOMLA_DB_NAME: {{DB_DATABASE}}
JOOMLA_DB_USER: {{DB_USER}}
JOOMLA_DB_PASSWORD: {{DB_PASSWORD}}
depends_on:
db:
condition: service_healthy
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
interval: 10s
timeout: 5s
retries: 3
# Nginx Web Server
nginx:
image: nginx:alpine
container_name: {{PROJECT_NAME}}_nginx
restart: unless-stopped
ports:
- "${APP_PORT:-8080}:80"
- "${APP_SSL_PORT:-8443}:443"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/joomla.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- {{PROJECT_NAME}}_network
# Database Server
db:
image: {{DB_IMAGE}}
container_name: {{PROJECT_NAME}}_db
restart: unless-stopped
ports:
- "${DB_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: {{DB_ROOT_PASSWORD}}
MYSQL_DATABASE: {{DB_DATABASE}}
MYSQL_USER: {{DB_USER}}
MYSQL_PASSWORD: {{DB_PASSWORD}}
volumes:
- db_data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{{DB_ROOT_PASSWORD}}"]
interval: 10s
timeout: 5s
retries: 5
# Joomla CLI Container
joomla-cli:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_cli
working_dir: /var/www/html
volumes:
- ./:/var/www/html
depends_on:
- db
environment:
JOOMLA_DB_HOST: db
JOOMLA_DB_NAME: {{DB_DATABASE}}
JOOMLA_DB_USER: {{DB_USER}}
JOOMLA_DB_PASSWORD: {{DB_PASSWORD}}
networks:
- {{PROJECT_NAME}}_network
entrypoint: ["php", "cli/joomla.php"]
# Usage: docker compose run --rm joomla-cli <command>
# Example: docker compose run --rm joomla-cli extension:list
# Email Testing
mailpit:
image: axllent/mailpit:latest
container_name: {{PROJECT_NAME}}_mailpit
restart: unless-stopped
ports:
- "${MAIL_UI_PORT:-8025}:8025"
- "${MAIL_SMTP_PORT:-1025}:1025"
networks:
- {{PROJECT_NAME}}_network
networks:
{{PROJECT_NAME}}_network:
driver: bridge
volumes:
db_data:
driver: local
# Laravel Docker Compose Template
# Template markers: {{PROJECT_NAME}}, {{PHP_VERSION}}, {{DB_IMAGE}}, {{APP_PORT}}, {{DB_PORT}}
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www
depends_on:
- db
- redis
networks:
- {{PROJECT_NAME}}_network
nginx:
image: nginx:alpine
ports:
- "${APP_PORT:-8080}:80"
volumes:
- ./:/var/www
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
networks:
- {{PROJECT_NAME}}_network
db:
image: {{DB_IMAGE}}
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-secret}
MYSQL_DATABASE: ${DB_DATABASE:-laravel}
MYSQL_USER: ${DB_USERNAME:-laravel}
MYSQL_PASSWORD: ${DB_PASSWORD:-secret}
volumes:
- db_data:/var/lib/mysql
ports:
- "${DB_PORT:-3306}:3306"
networks:
- {{PROJECT_NAME}}_network
redis:
image: redis:alpine
volumes:
- redis_data:/data
networks:
- {{PROJECT_NAME}}_network
mailpit:
image: axllent/mailpit:latest
ports:
- "${MAIL_PORT:-1025}:1025"
- "${MAIL_UI_PORT:-8025}:8025"
networks:
- {{PROJECT_NAME}}_network
# Uncomment for queue workers
# worker:
# build:
# context: .
# dockerfile: Dockerfile
# command: php artisan queue:work --sleep=3 --tries=3
# volumes:
# - ./:/var/www
# depends_on:
# - db
# - redis
# networks:
# - {{PROJECT_NAME}}_network
networks:
{{PROJECT_NAME}}_network:
driver: bridge
volumes:
db_data:
redis_data:
# WordPress Docker Compose Template
# Generated by docker-local-dev skill
# Template variables: {{PROJECT_NAME}}, {{PHP_VERSION}}, {{DB_IMAGE}}, {{DB_PORT}}, etc.
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
# WordPress Application with PHP-FPM
app:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_app
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./:/var/www/html
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini:ro
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: {{DB_USER}}
WORDPRESS_DB_PASSWORD: {{DB_PASSWORD}}
WORDPRESS_DB_NAME: {{DB_DATABASE}}
WORDPRESS_DEBUG: '${WP_DEBUG:-1}'
WORDPRESS_CONFIG_EXTRA: |
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);
define('SAVEQUERIES', true);
depends_on:
db:
condition: service_healthy
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
interval: 10s
timeout: 5s
retries: 3
# Nginx Web Server
nginx:
image: nginx:alpine
container_name: {{PROJECT_NAME}}_nginx
restart: unless-stopped
ports:
- "${APP_PORT:-8080}:80"
- "${APP_SSL_PORT:-8443}:443"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/wordpress.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- {{PROJECT_NAME}}_network
# Database Server
db:
image: {{DB_IMAGE}}
container_name: {{PROJECT_NAME}}_db
restart: unless-stopped
ports:
- "${DB_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: {{DB_ROOT_PASSWORD}}
MYSQL_DATABASE: {{DB_DATABASE}}
MYSQL_USER: {{DB_USER}}
MYSQL_PASSWORD: {{DB_PASSWORD}}
volumes:
- db_data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{{DB_ROOT_PASSWORD}}"]
interval: 10s
timeout: 5s
retries: 5
# WP-CLI Container (for running WordPress commands)
wpcli:
image: wordpress:cli
container_name: {{PROJECT_NAME}}_wpcli
volumes:
- ./:/var/www/html
depends_on:
- db
- app
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: {{DB_USER}}
WORDPRESS_DB_PASSWORD: {{DB_PASSWORD}}
WORDPRESS_DB_NAME: {{DB_DATABASE}}
networks:
- {{PROJECT_NAME}}_network
entrypoint: ["wp", "--allow-root"]
# Usage: docker compose run --rm wpcli <command>
# Example: docker compose run --rm wpcli plugin list
# Redis Cache (Optional - uncomment if needed)
# redis:
# image: redis:7-alpine
# container_name: {{PROJECT_NAME}}_redis
# restart: unless-stopped
# ports:
# - "${REDIS_PORT:-6379}:6379"
# volumes:
# - redis_data:/data
# networks:
# - {{PROJECT_NAME}}_network
# healthcheck:
# test: ["CMD", "redis-cli", "ping"]
# interval: 10s
# timeout: 5s
# retries: 3
# Email Testing (Optional - choose MailHog or Mailpit)
mailpit:
image: axllent/mailpit:latest
container_name: {{PROJECT_NAME}}_mailpit
restart: unless-stopped
ports:
- "${MAIL_UI_PORT:-8025}:8025"
- "${MAIL_SMTP_PORT:-1025}:1025"
networks:
- {{PROJECT_NAME}}_network
# phpMyAdmin (Optional - for database management)
# phpmyadmin:
# image: phpmyadmin:latest
# container_name: {{PROJECT_NAME}}_phpmyadmin
# restart: unless-stopped
# ports:
# - "${PMA_PORT:-8081}:80"
# environment:
# PMA_HOST: db
# PMA_USER: root
# PMA_PASSWORD: {{DB_ROOT_PASSWORD}}
# depends_on:
# - db
# networks:
# - {{PROJECT_NAME}}_network
networks:
{{PROJECT_NAME}}_network:
driver: bridge
# For Nginx Proxy Manager or multi-project setups:
# external: true
# name: shared_network
volumes:
db_data:
driver: local
# redis_data:
# driver: local
# Python Docker Compose Template
# Generated by docker-local-dev skill
# Supports: Django, FastAPI, Flask
# Template variables: {{PROJECT_NAME}}, {{PYTHON_VERSION}}, {{DB_IMAGE}}, etc.
# Note: No 'version' field needed - deprecated in Docker Compose v2
services:
# Python Application (Django/FastAPI/Flask)
app:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_app
restart: unless-stopped
working_dir: /app
volumes:
- ./:/app
- static_files:/app/staticfiles # For Django static files
environment:
PYTHONUNBUFFERED: 1
DJANGO_SETTINGS_MODULE: {{PROJECT_NAME}}.settings
DATABASE_URL: postgres://{{DB_USER}}:{{DB_PASSWORD}}@db:5432/{{DB_DATABASE}}
REDIS_URL: redis://redis:6379/0
SECRET_KEY: {{SECRET_KEY}}
DEBUG: "True"
ALLOWED_HOSTS: "*"
ports:
- "${APP_PORT:-8000}:8000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- {{PROJECT_NAME}}_network
# For Django development:
command: python manage.py runserver 0.0.0.0:8000
# For FastAPI with uvicorn:
# command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# For production with Gunicorn:
# command: gunicorn {{PROJECT_NAME}}.wsgi:application --bind 0.0.0.0:8000 --workers 4
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
interval: 10s
timeout: 5s
retries: 3
# Nginx Reverse Proxy
nginx:
image: nginx:alpine
container_name: {{PROJECT_NAME}}_nginx
restart: unless-stopped
ports:
- "${NGINX_PORT:-8080}:80"
- "${NGINX_SSL_PORT:-8443}:443"
volumes:
- ./docker/nginx/python-wsgi.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
- static_files:/var/www/static:ro
- ./media:/var/www/media:ro
depends_on:
- app
networks:
- {{PROJECT_NAME}}_network
# PostgreSQL Database (Recommended for Django)
db:
image: postgres:16-alpine
container_name: {{PROJECT_NAME}}_db
restart: unless-stopped
ports:
- "${DB_PORT:-5432}:5432"
environment:
POSTGRES_DB: {{DB_DATABASE}}
POSTGRES_USER: {{DB_USER}}
POSTGRES_PASSWORD: {{DB_PASSWORD}}
volumes:
- db_data:/var/lib/postgresql/data
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{DB_USER}} -d {{DB_DATABASE}}"]
interval: 10s
timeout: 5s
retries: 5
# Redis Cache/Message Broker
redis:
image: redis:7-alpine
container_name: {{PROJECT_NAME}}_redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
networks:
- {{PROJECT_NAME}}_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Celery Worker (Optional - for background tasks)
celery:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_celery
restart: unless-stopped
working_dir: /app
volumes:
- ./:/app
environment:
PYTHONUNBUFFERED: 1
DJANGO_SETTINGS_MODULE: {{PROJECT_NAME}}.settings
DATABASE_URL: postgres://{{DB_USER}}:{{DB_PASSWORD}}@db:5432/{{DB_DATABASE}}
REDIS_URL: redis://redis:6379/0
depends_on:
- db
- redis
networks:
- {{PROJECT_NAME}}_network
command: celery -A {{PROJECT_NAME}} worker -l info
# Celery Beat (Optional - for scheduled tasks)
celery-beat:
build:
context: .
dockerfile: Dockerfile
container_name: {{PROJECT_NAME}}_celery_beat
restart: unless-stopped
working_dir: /app
volumes:
- ./:/app
environment:
PYTHONUNBUFFERED: 1
DJANGO_SETTINGS_MODULE: {{PROJECT_NAME}}.settings
DATABASE_URL: postgres://{{DB_USER}}:{{DB_PASSWORD}}@db:5432/{{DB_DATABASE}}
REDIS_URL: redis://redis:6379/0
depends_on:
- db
- redis
networks:
- {{PROJECT_NAME}}_network
command: celery -A {{PROJECT_NAME}} beat -l info
# Email Testing
mailpit:
image: axllent/mailpit:latest
container_name: {{PROJECT_NAME}}_mailpit
restart: unless-stopped
ports:
- "${MAIL_UI_PORT:-8025}:8025"
- "${MAIL_SMTP_PORT:-1025}:1025"
networks:
- {{PROJECT_NAME}}_network
networks:
{{PROJECT_NAME}}_network:
driver: bridge
# For multi-project or microservices:
# external: true
# name: shared_network
volumes:
db_data:
driver: local
redis_data:
driver: local
static_files:
driver: local
# Node.js Dockerfile
# Template markers: {{NODE_VERSION}}, {{PACKAGE_MANAGER}}, {{START_COMMAND}}
FROM node:{{NODE_VERSION}}-alpine
# Install build dependencies (for native modules)
RUN apk add --no-cache \
python3 \
make \
g++ \
git
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
{{#if_yarn}}
COPY yarn.lock ./
{{/if_yarn}}
{{#if_pnpm}}
COPY pnpm-lock.yaml ./
{{/if_pnpm}}
# Install dependencies
{{#if_npm}}
RUN npm ci
{{/if_npm}}
{{#if_yarn}}
RUN yarn install --frozen-lockfile
{{/if_yarn}}
{{#if_pnpm}}
RUN npm install -g pnpm && pnpm install --frozen-lockfile
{{/if_pnpm}}
# Copy application code
COPY . .
# Build application (if needed)
# RUN npm run build
# Expose port
EXPOSE {{PORT}}
# Start command
CMD ["{{START_COMMAND}}"]
# For development with hot reload:
# CMD ["npm", "run", "dev"]
# Drupal PHP-FPM Dockerfile
# Includes Drush
FROM php:{{PHP_VERSION}}-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
zip \
unzip \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libwebp-dev \
libzip-dev \
libicu-dev \
libxml2-dev \
libmagickwand-dev \
mariadb-client \
&& rm -rf /var/lib/apt/lists/*
# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install -j$(nproc) \
pdo_mysql \
gd \
zip \
intl \
xml \
opcache
# Install ImageMagick
RUN pecl install imagick && docker-php-ext-enable imagick
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Install Drush launcher
RUN curl -OL https://github.com/drush-ops/drush-launcher/releases/latest/download/drush.phar \
&& chmod +x drush.phar \
&& mv drush.phar /usr/local/bin/drush
# Configure opcache for development
RUN echo 'opcache.enable=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.memory_consumption=256' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.interned_strings_buffer=16' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.max_accelerated_files=10000' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.validate_timestamps=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.revalidate_freq=0' >> /usr/local/etc/php/conf.d/opcache.ini
# Configure PHP
RUN echo 'memory_limit=512M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'upload_max_filesize=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'post_max_size=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'max_execution_time=300' >> /usr/local/etc/php/conf.d/docker.ini
# Set working directory
WORKDIR /var/www/html
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 9000
CMD ["php-fpm"]
# Joomla PHP-FPM Dockerfile
FROM php:{{PHP_VERSION}}-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
zip \
unzip \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libzip-dev \
libicu-dev \
libxml2-dev \
libldap2-dev \
libmagickwand-dev \
mariadb-client \
&& rm -rf /var/lib/apt/lists/*
# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
mysqli \
pdo_mysql \
gd \
zip \
intl \
xml \
ldap \
opcache
# Install ImageMagick
RUN pecl install imagick && docker-php-ext-enable imagick
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Configure opcache for development
RUN echo 'opcache.enable=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.memory_consumption=256' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.interned_strings_buffer=16' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.max_accelerated_files=10000' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.validate_timestamps=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.revalidate_freq=0' >> /usr/local/etc/php/conf.d/opcache.ini
# Configure PHP
RUN echo 'memory_limit=256M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'upload_max_filesize=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'post_max_size=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'max_execution_time=300' >> /usr/local/etc/php/conf.d/docker.ini
# Set working directory
WORKDIR /var/www/html
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 9000
CMD ["php-fpm"]
# Laravel PHP-FPM Dockerfile
# Template markers: {{PHP_VERSION}}, {{EXTENSIONS}}
FROM php:{{PHP_VERSION}}-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
zip \
unzip \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libmagickwand-dev \
libonig-dev \
libxml2-dev \
libzip-dev \
libicu-dev \
mariadb-client \
&& rm -rf /var/lib/apt/lists/*
# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_mysql \
mbstring \
exif \
pcntl \
bcmath \
gd \
zip \
intl \
xml \
opcache
# Install ImageMagick
RUN pecl install imagick && docker-php-ext-enable imagick
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Configure opcache for development
RUN echo 'opcache.enable=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.memory_consumption=256' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.interned_strings_buffer=16' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.max_accelerated_files=10000' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.validate_timestamps=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.revalidate_freq=0' >> /usr/local/etc/php/conf.d/opcache.ini
# Configure PHP for development
RUN echo 'memory_limit=512M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'upload_max_filesize=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'post_max_size=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'max_execution_time=60' >> /usr/local/etc/php/conf.d/docker.ini
# Set working directory
WORKDIR /var/www
# Set proper permissions
RUN chown -R www-data:www-data /var/www
# Switch to non-root user
USER www-data
# Expose PHP-FPM port
EXPOSE 9000
CMD ["php-fpm"]
# WordPress PHP-FPM Dockerfile
# Includes WP-CLI and debug extensions
FROM php:{{PHP_VERSION}}-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
zip \
unzip \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libwebp-dev \
libzip-dev \
libicu-dev \
libxml2-dev \
libmagickwand-dev \
less \
mariadb-client \
&& rm -rf /var/lib/apt/lists/*
# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install -j$(nproc) \
mysqli \
pdo_mysql \
gd \
zip \
intl \
xml \
exif \
opcache
# Install ImageMagick
RUN pecl install imagick && docker-php-ext-enable imagick
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install WP-CLI
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
&& chmod +x wp-cli.phar \
&& mv wp-cli.phar /usr/local/bin/wp
# Configure opcache for development
RUN echo 'opcache.enable=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.memory_consumption=256' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.interned_strings_buffer=16' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.max_accelerated_files=10000' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.validate_timestamps=1' >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo 'opcache.revalidate_freq=0' >> /usr/local/etc/php/conf.d/opcache.ini
# Configure PHP for WordPress
RUN echo 'memory_limit=256M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'upload_max_filesize=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'post_max_size=100M' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'max_execution_time=300' >> /usr/local/etc/php/conf.d/docker.ini \
&& echo 'max_input_vars=3000' >> /usr/local/etc/php/conf.d/docker.ini
# Set working directory
WORKDIR /var/www/html
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html
EXPOSE 9000
CMD ["php-fpm"]
# Django Dockerfile
# Template markers: {{PYTHON_VERSION}}
FROM python:{{PYTHON_VERSION}}-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
libffi-dev \
libssl-dev \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements first (for layer caching)
COPY requirements.txt ./
# OR for Poetry:
# COPY pyproject.toml poetry.lock ./
# Install Python dependencies
RUN pip install --upgrade pip \
&& pip install -r requirements.txt
# OR for Poetry:
# RUN pip install poetry \
# && poetry config virtualenvs.create false \
# && poetry install --no-interaction --no-ansi
# Install Gunicorn
RUN pip install gunicorn
# Copy application code
COPY . .
# Collect static files (for production)
# RUN python manage.py collectstatic --noinput
# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 8000
# Development command
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
# Production command (uncomment for production):
# CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "myproject.wsgi:application"]
# FastAPI Dockerfile
# Template markers: {{PYTHON_VERSION}}
FROM python:{{PYTHON_VERSION}}-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements first (for layer caching)
COPY requirements.txt ./
# OR for Poetry:
# COPY pyproject.toml poetry.lock ./
# Install Python dependencies
RUN pip install --upgrade pip \
&& pip install -r requirements.txt
# OR for Poetry:
# RUN pip install poetry \
# && poetry config virtualenvs.create false \
# && poetry install --no-interaction --no-ansi
# Install Uvicorn
RUN pip install uvicorn[standard]
# Copy application code
COPY . .
# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 8000
# Development command with hot reload
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# Production command (uncomment for production):
# CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
# Docker Commands Quick Reference
## Container Management
```bash
# Start all containers
docker compose up -d
# Stop all containers
docker compose down
# Restart all containers
docker compose restart
# View running containers
docker compose ps
# View all containers (including stopped)
docker compose ps -a
```
## Logs
```bash
# View all logs
docker compose logs
# Follow logs in real-time
docker compose logs -f
# View logs for specific service
docker compose logs app
docker compose logs -f nginx
```
## Executing Commands
```bash
# Enter container shell
docker compose exec app bash
docker compose exec app sh # For Alpine
# Run single command
docker compose exec app php artisan migrate
docker compose exec app npm run build
docker compose exec app python manage.py migrate
```
## Database
```bash
# MySQL/MariaDB CLI
docker compose exec db mysql -u root -p
# PostgreSQL CLI
docker compose exec db psql -U postgres
# Import database
docker compose exec -T db mysql -u root -p database < dump.sql
# Export database
docker compose exec db mysqldump -u root -p database > dump.sql
```
## Building
```bash
# Build/rebuild containers
docker compose build
# Build without cache
docker compose build --no-cache
# Pull latest images
docker compose pull
```
## Cleanup
```bash
# Stop and remove containers
docker compose down
# Remove containers AND volumes (data loss!)
docker compose down -v
# Remove unused Docker resources
docker system prune
# Remove all unused images
docker image prune -a
```
## Troubleshooting
```bash
# Check configuration
docker compose config
# Check container resource usage
docker stats
# Inspect container
docker compose exec app cat /etc/hosts
# Check network
docker network ls
docker network inspect <network_name>
```
# Contributing to docker-local-dev
Thank you for your interest in improving the docker-local-dev skill!
## Adding Support for New Tech Stacks
### 1. Detection Patterns
Add detection logic to `scripts/detect-stack.sh`:
```bash
# Example: Adding Ruby on Rails detection
if file_exists "Gemfile" && string_in_file "rails" "Gemfile"; then
LANGUAGE="ruby"
FRAMEWORK="rails"
# Detect version
FRAMEWORK_VERSION=$(grep 'rails' "$PROJECT_ROOT/Gemfile" | grep -oE '[0-9]+\.[0-9]+' | head -1)
fi
```
### 2. Dockerfile Template
Create `assets/templates/dockerfile/ruby-rails.dockerfile`:
```dockerfile
FROM ruby:{{RUBY_VERSION}}
# Install dependencies
RUN apt-get update && apt-get install -y \
nodejs \
yarn \
libpq-dev
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
```
### 3. Docker Compose Template
Create `assets/templates/docker-compose/ruby-rails.yml`:
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "${APP_PORT:-3000}:3000"
volumes:
- ./:/app
depends_on:
- db
environment:
DATABASE_URL: postgres://...
db:
image: postgres:16-alpine
# ...
```
### 4. Nginx Template (if needed)
Create `assets/templates/nginx/rails.conf`:
```nginx
upstream rails {
server app:3000;
}
server {
listen 80;
# ...
}
```
### 5. Update Documentation
Add your stack to `assets/templates/docs/SUPPORTED-STACKS.md`:
```markdown
### Ruby on Rails (Full Support)
| Feature | Supported |
|---------|-----------|
| Ruby 3.1/3.2/3.3 | Yes |
| PostgreSQL | Yes |
| Redis | Yes |
| Sidekiq | Yes |
```
## Testing Your Changes
1. Create a test project with your stack
2. Run the detection script:
```bash
./scripts/detect-stack.sh /path/to/test/project
```
3. Verify the generated Docker files work:
```bash
docker compose config
docker compose up -d
./scripts/health-check.sh
```
## Pull Request Guidelines
1. Test with a real project
2. Include detection patterns
3. Include all necessary templates
4. Update documentation
5. Follow existing code style
## Questions?
Open an issue on GitHub with your questions or suggestions.
Supported Tech Stacks
This document lists all officially supported tech stacks and their features.
PHP Stacks
Laravel (Full Support)
| Feature | Supported |
|---|---|
| PHP 8.1/8.2/8.3 | Yes |
| MySQL/MariaDB/PostgreSQL | Yes |
| Redis | Yes |
| Queue Workers (Supervisor) | Yes |
| Scheduler | Yes |
| Horizon | Yes |
| Mailpit/MailHog | Yes |
| Opcache (dev optimized) | Yes |
WordPress (Full Support)
| Feature | Supported |
|---|---|
| PHP 8.0/8.1/8.2/8.3 | Yes |
| WP-CLI | Yes |
| MySQL/MariaDB | Yes |
| Redis Object Cache | Yes |
| Debug Plugins | Yes |
| ImageMagick | Yes |
Drupal (Full Support)
| Feature | Supported |
|---|---|
| PHP 8.1/8.2/8.3 | Yes |
| Drush | Yes |
| MySQL/MariaDB/PostgreSQL | Yes |
| Redis | Yes |
| Development Services | Yes |
Joomla (Full Support)
| Feature | Supported |
|---|---|
| PHP 8.1/8.2/8.3 | Yes |
| MySQL/MariaDB | Yes |
| Debug Mode | Yes |
| CLI Tools | Yes |
Node.js Stacks
Express/NestJS/Fastify (Full Support)
| Feature | Supported |
|---|---|
| Node 18/20/22 | Yes |
| npm/yarn/pnpm/bun | Yes |
| PM2 | Yes |
| Hot Reload | Yes |
| MySQL/PostgreSQL | Yes |
| Redis | Yes |
Next.js (Full Support)
| Feature | Supported |
|---|---|
| Node 18/20/22 | Yes |
| Development Mode | Yes |
| Production Build | Yes |
| API Routes | Yes |
Python Stacks
Django (Full Support)
| Feature | Supported |
|---|---|
| Python 3.10/3.11/3.12 | Yes |
| pip/poetry/pipenv | Yes |
| Gunicorn | Yes |
| Celery | Yes |
| PostgreSQL/MySQL | Yes |
| Redis | Yes |
| Static Files | Yes |
FastAPI (Full Support)
| Feature | Supported |
|---|---|
| Python 3.10/3.11/3.12 | Yes |
| Uvicorn | Yes |
| Hot Reload | Yes |
| PostgreSQL/MySQL | Yes |
| Redis | Yes |
Flask (Partial Support)
| Feature | Supported |
|---|---|
| Python 3.10/3.11/3.12 | Yes |
| Gunicorn | Yes |
| Database | Yes |
| Redis | Partial |
Adding Support for New Stacks
If your stack is not listed, the skill will: 1. Warn you that the stack is not officially supported 2. Proceed with generic configuration 3. Encourage you to contribute improvements
See CONTRIBUTING.md for how to add support.
# Docker Development Environment
Generated by docker-local-dev skill.
## Quick Commands
### Start containers
```bash
docker compose up -d
```
### Stop containers
```bash
docker compose down
```
### View logs
```bash
docker compose logs -f
# Or specific service:
docker compose logs -f app
```
### Restart services
```bash
docker compose restart
# Or specific service:
docker compose restart app
```
## Accessing Services
| Service | URL/Host | Credentials |
|---------|----------|-------------|
| Web | http://localhost:{{APP_PORT}} | - |
| Database | localhost:{{DB_PORT}} | {{DB_USER}} / {{DB_PASSWORD}} |
| Redis | localhost:{{REDIS_PORT}} | - |
| Mail UI | http://localhost:{{MAIL_UI_PORT}} | - |
## Container Networking (Important)
When your app runs inside Docker, `localhost` points to the **app container**, not your database container.
Use the **service name** from `docker-compose.yml` instead.
Common defaults:
- Database host from app container: `db`
- Redis host from app container: `redis`
- Mail host from app container: `mailpit`
Laravel example (`.env` inside the container):
```
DB_HOST=db
DB_PORT=3306
REDIS_HOST=redis
MAIL_HOST=mailpit
```
## Stack-Specific Commands
### {{FRAMEWORK}} Commands
```bash
# Enter app container
docker compose exec app bash
# Run artisan commands (Laravel)
docker compose exec app php artisan <command>
# Run composer
docker compose exec app composer <command>
# Run npm/yarn
docker compose exec app npm <command>
```
## Database Access
### Using CLI
```bash
docker compose exec db mysql -u root -p
# Password: {{DB_PASSWORD}}
```
### Using GUI Tools
Connect with your preferred SQL client (DBeaver, DataGrip, etc.):
- Host: localhost
- Port: {{DB_PORT}}
- User: {{DB_USER}}
- Password: {{DB_PASSWORD}}
- Database: {{DB_DATABASE}}
## Troubleshooting
### Container won't start
```bash
# Check logs
docker compose logs
# Verify configuration
docker compose config
```
### Port already in use
```bash
# Find what's using the port
lsof -i :{{APP_PORT}}
# Use different port in .env
APP_PORT=8081
```
### Permission issues
```bash
# Fix ownership inside container
docker compose exec app chown -R www-data:www-data /var/www
```
### Database connection failed
```bash
# Check database is running
docker compose ps
# Test connection
docker compose exec db mysqladmin ping -u root -p
```
## Maintenance
### Clear all data and start fresh
```bash
docker compose down -v # Removes volumes too!
docker compose up -d
```
### Rebuild containers
```bash
docker compose build --no-cache
docker compose up -d
```
### Update images
```bash
docker compose pull
docker compose up -d
```
# Drupal Nginx Configuration Template
# Generated by docker-local-dev skill
upstream php-fpm {
server app:9000;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
root /var/www/html/web;
index index.php index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Drupal location handling
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Block access to private files
location ~ ^/sites/.*/private/ {
return 403;
}
# Block access to files and directories with sensitive information
location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config|yarn\.lock|package\.json)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
deny all;
return 404;
}
# Handle Drupal clean URLs
location / {
try_files $uri /index.php?$query_string;
}
# Handle PHP files
location ~ '\.php$|^/update.php' {
fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
# Security: Return 404 if .php file doesn't exist
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param HTTP_PROXY "";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param QUERY_STRING $query_string;
fastcgi_intercept_errors on;
fastcgi_pass php-fpm;
fastcgi_index index.php;
# Timeouts
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
# Handle private files through Drupal
location ~ ^/system/files/ {
try_files $uri /index.php?$query_string;
}
# Handle image styles through Drupal
location ~ ^/sites/.*/files/styles/ {
try_files $uri @rewrite;
}
location @rewrite {
rewrite ^ /index.php;
}
# Deny access to .ht files
location ~ /\.ht {
deny all;
}
# Static files caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires max;
log_not_found off;
access_log off;
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
# Joomla Nginx Configuration Template
# Generated by docker-local-dev skill
upstream php-fpm {
server app:9000;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
root /var/www/html;
index index.php index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Max upload size
client_max_body_size 64M;
# Block access to sensitive files
location ~* /(\.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
location ~* /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# Deny access to htaccess files
location ~ /\.ht {
deny all;
}
# Block access to configuration.php
location = /configuration.php {
deny all;
}
# Block access to administrator/logs
location ~ ^/administrator/logs/ {
deny all;
}
# Handle Joomla SEF URLs
location / {
try_files $uri $uri/ /index.php?$args;
}
# Handle PHP files
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Security: Return 404 if .php file doesn't exist
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php-fpm;
fastcgi_index index.php;
# Timeouts
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
# Static files caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires max;
log_not_found off;
access_log off;
}
# Media files
location ~* \.(mp3|mp4|ogg|ogv|webm)$ {
expires max;
log_not_found off;
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
server {
listen 80;
server_name localhost;
root /var/www/public;
index index.php index.html;
client_max_body_size 100M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
location ~ /\.ht {
deny all;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
# Node.js Nginx Reverse Proxy Configuration Template
# Generated by docker-local-dev skill
# Supports: Express, NestJS, Fastify, Next.js
upstream nodejs {
server app:3000;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Max upload size
client_max_body_size 50M;
# Proxy settings
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Static files (if served from Nginx)
location /static/ {
alias /var/www/public/static/;
expires max;
access_log off;
}
location /public/ {
alias /var/www/public/;
expires max;
access_log off;
}
# Next.js static files
location /_next/static/ {
proxy_pass http://nodejs;
expires max;
access_log off;
}
# WebSocket support (for hot reload, Socket.io, etc.)
location /socket.io/ {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# HMR WebSocket for development
location /_next/webpack-hmr {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# API routes
location /api/ {
proxy_pass http://nodejs;
proxy_buffering off;
}
# Health check endpoint
location /health {
proxy_pass http://nodejs;
access_log off;
}
# Main application
location / {
proxy_pass http://nodejs;
proxy_buffering off;
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
# Python WSGI/ASGI Nginx Configuration Template
# Generated by docker-local-dev skill
# Supports: Django (Gunicorn), FastAPI (Uvicorn), Flask
upstream python_app {
server app:8000;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Max upload size
client_max_body_size 100M;
# Proxy settings
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Django static files
location /static/ {
alias /var/www/static/;
expires 30d;
access_log off;
add_header Cache-Control "public, immutable";
}
# Django media files (user uploads)
location /media/ {
alias /var/www/media/;
expires 7d;
access_log off;
}
# Favicon
location = /favicon.ico {
alias /var/www/static/favicon.ico;
access_log off;
log_not_found off;
}
# Robots.txt
location = /robots.txt {
alias /var/www/static/robots.txt;
access_log off;
log_not_found off;
}
# WebSocket support (for Django Channels, FastAPI WebSockets)
location /ws/ {
proxy_pass http://python_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# Health check endpoint
location /health/ {
proxy_pass http://python_app;
access_log off;
}
# Django admin
location /admin/ {
proxy_pass http://python_app;
proxy_buffering off;
}
# API routes
location /api/ {
proxy_pass http://python_app;
proxy_buffering off;
}
# FastAPI docs (Swagger UI)
location /docs {
proxy_pass http://python_app;
}
location /redoc {
proxy_pass http://python_app;
}
location /openapi.json {
proxy_pass http://python_app;
}
# Main application
location / {
proxy_pass http://python_app;
proxy_buffering off;
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html;
client_max_body_size 100M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
location ~ /\.ht {
deny all;
}
location = /wp-config.php {
deny all;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
[program:cron]
command=/usr/sbin/cron -f
autostart=true
autorestart=true
stdout_logfile=/var/log/cron.log
stderr_logfile=/var/log/cron-error.log
[program:laravel-scheduler]
command=/bin/sh -c "while [ true ]; do php /var/www/artisan schedule:run --verbose --no-interaction; sleep 60; done"
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/scheduler.log
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/worker.log
stopwaitsecs=3600
; Node.js Cron Jobs via Supervisor Configuration Template
; Generated by docker-local-dev skill
; For running scheduled tasks in Node.js applications
; Option 1: Using node-cron library (recommended for simple tasks)
; Install: npm install node-cron
; Create a cron.js file that uses node-cron internally
[program:node-cron]
command=node /app/scripts/cron.js
directory=/app
user=node
autostart=true
autorestart=true
startsecs=5
startretries=3
stdout_logfile=/var/log/supervisor/node-cron-stdout.log
stderr_logfile=/var/log/supervisor/node-cron-stderr.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
stdout_logfile_backups=3
stderr_logfile_backups=3
environment=NODE_ENV="%(ENV_NODE_ENV)s"
; Option 2: Using Agenda.js for job scheduling (recommended for complex tasks)
; [program:agenda]
; command=node /app/scripts/agenda-worker.js
; directory=/app
; user=node
; autostart=true
; autorestart=true
; startsecs=5
; startretries=3
; stdout_logfile=/var/log/supervisor/agenda-stdout.log
; stderr_logfile=/var/log/supervisor/agenda-stderr.log
; environment=NODE_ENV="%(ENV_NODE_ENV)s"
; Option 3: Using Bull Queue with scheduled jobs
; [program:bull-scheduler]
; command=node /app/scripts/scheduler.js
; directory=/app
; user=node
; autostart=true
; autorestart=true
; startsecs=5
; startretries=3
; stdout_logfile=/var/log/supervisor/bull-scheduler-stdout.log
; stderr_logfile=/var/log/supervisor/bull-scheduler-stderr.log
; environment=NODE_ENV="%(ENV_NODE_ENV)s"
; Example cron.js using node-cron:
; const cron = require('node-cron');
;
; // Run every minute
; cron.schedule('* * * * *', () => {
; console.log('Running task every minute');
; });
;
; // Run every hour
; cron.schedule('0 * * * *', () => {
; console.log('Running hourly task');
; });
;
; // Run every day at midnight
; cron.schedule('0 0 * * *', () => {
; console.log('Running daily task');
; });
;
; console.log('Cron jobs started');
; PM2 via Supervisor Configuration Template
; Generated by docker-local-dev skill
; For Node.js applications using PM2 as process manager
[program:pm2]
command=pm2-runtime start ecosystem.config.js
directory=/app
user=node
autostart=true
autorestart=true
startsecs=10
startretries=3
stopwaitsecs=30
stdout_logfile=/var/log/supervisor/pm2-stdout.log
stderr_logfile=/var/log/supervisor/pm2-stderr.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
stdout_logfile_backups=5
stderr_logfile_backups=5
environment=NODE_ENV="%(ENV_NODE_ENV)s",HOME="/home/node"
; Alternative: Direct node process without PM2
; [program:node-app]
; command=node /app/dist/main.js
; directory=/app
; user=node
; autostart=true
; autorestart=true
; startsecs=10
; startretries=3
; stdout_logfile=/var/log/supervisor/node-stdout.log
; stderr_logfile=/var/log/supervisor/node-stderr.log
; environment=NODE_ENV="production"
; PM2 ecosystem.config.js example:
; module.exports = {
; apps: [{
; name: 'app',
; script: 'dist/main.js',
; instances: 'max',
; exec_mode: 'cluster',
; env: {
; NODE_ENV: 'production'
; }
; }]
; };
; Python Celery Workers via Supervisor Configuration Template
; Generated by docker-local-dev skill
; For Django, FastAPI, and Flask applications using Celery
[program:celery-worker]
command=celery -A %(ENV_PROJECT_NAME)s worker -l info --concurrency=2
directory=/app
user=www-data
autostart=true
autorestart=true
startsecs=10
startretries=3
stopwaitsecs=600
stopasgroup=true
killasgroup=true
stdout_logfile=/var/log/supervisor/celery-worker-stdout.log
stderr_logfile=/var/log/supervisor/celery-worker-stderr.log
stdout_logfile_maxbytes=50MB
stderr_logfile_maxbytes=50MB
stdout_logfile_backups=5
stderr_logfile_backups=5
environment=
PYTHONUNBUFFERED="1",
DJANGO_SETTINGS_MODULE="%(ENV_DJANGO_SETTINGS_MODULE)s",
DATABASE_URL="%(ENV_DATABASE_URL)s",
REDIS_URL="%(ENV_REDIS_URL)s"
; Celery Beat (Scheduler) - for periodic tasks
[program:celery-beat]
command=celery -A %(ENV_PROJECT_NAME)s beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
directory=/app
user=www-data
autostart=true
autorestart=true
startsecs=10
startretries=3
stopwaitsecs=30
stdout_logfile=/var/log/supervisor/celery-beat-stdout.log
stderr_logfile=/var/log/supervisor/celery-beat-stderr.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
stdout_logfile_backups=3
stderr_logfile_backups=3
environment=
PYTHONUNBUFFERED="1",
DJANGO_SETTINGS_MODULE="%(ENV_DJANGO_SETTINGS_MODULE)s",
DATABASE_URL="%(ENV_DATABASE_URL)s",
REDIS_URL="%(ENV_REDIS_URL)s"
; Flower (Celery monitoring) - Optional
; [program:celery-flower]
; command=celery -A %(ENV_PROJECT_NAME)s flower --port=5555
; directory=/app
; user=www-data
; autostart=true
; autorestart=true
; startsecs=10
; stdout_logfile=/var/log/supervisor/celery-flower-stdout.log
; stderr_logfile=/var/log/supervisor/celery-flower-stderr.log
; environment=
; PYTHONUNBUFFERED="1",
; DJANGO_SETTINGS_MODULE="%(ENV_DJANGO_SETTINGS_MODULE)s"
; Group all Celery processes
[group:celery]
programs=celery-worker,celery-beat
priority=999
; FastAPI with Celery example:
; command=celery -A app.celery_app worker -l info --concurrency=2
;
; Flask with Celery example:
; command=celery -A app.celery worker -l info --concurrency=2
; Python Cron/Scheduled Tasks via Supervisor Configuration Template
; Generated by docker-local-dev skill
; For Python applications without Celery (using APScheduler or cron)
; Option 1: APScheduler (recommended for in-process scheduling)
; pip install apscheduler
[program:python-scheduler]
command=python /app/scripts/scheduler.py
directory=/app
user=www-data
autostart=true
autorestart=true
startsecs=5
startretries=3
stopwaitsecs=30
stdout_logfile=/var/log/supervisor/python-scheduler-stdout.log
stderr_logfile=/var/log/supervisor/python-scheduler-stderr.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
stdout_logfile_backups=3
stderr_logfile_backups=3
environment=
PYTHONUNBUFFERED="1",
DJANGO_SETTINGS_MODULE="%(ENV_DJANGO_SETTINGS_MODULE)s"
; Option 2: Django management command for cron tasks
; [program:django-cron]
; command=python manage.py runcrons
; directory=/app
; user=www-data
; autostart=true
; autorestart=true
; startsecs=5
; stdout_logfile=/var/log/supervisor/django-cron-stdout.log
; stderr_logfile=/var/log/supervisor/django-cron-stderr.log
; environment=PYTHONUNBUFFERED="1",DJANGO_SETTINGS_MODULE="%(ENV_DJANGO_SETTINGS_MODULE)s"
; Option 3: Simple loop-based scheduler
; [program:simple-scheduler]
; command=python /app/scripts/simple_scheduler.py
; directory=/app
; user=www-data
; autostart=true
; autorestart=true
; startsecs=5
; stdout_logfile=/var/log/supervisor/simple-scheduler-stdout.log
; stderr_logfile=/var/log/supervisor/simple-scheduler-stderr.log
; environment=PYTHONUNBUFFERED="1"
; Example APScheduler script (scheduler.py):
; from apscheduler.schedulers.blocking import BlockingScheduler
; from apscheduler.triggers.cron import CronTrigger
;
; scheduler = BlockingScheduler()
;
; @scheduler.scheduled_job('interval', minutes=1)
; def job_every_minute():
; print('Job running every minute')
;
; @scheduler.scheduled_job(CronTrigger(hour=0, minute=0))
; def daily_job():
; print('Daily job at midnight')
;
; @scheduler.scheduled_job(CronTrigger(hour='*/2'))
; def job_every_2_hours():
; print('Job running every 2 hours')
;
; if __name__ == '__main__':
; print('Starting scheduler...')
; scheduler.start()
; Example simple_scheduler.py (without external dependencies):
; import time
; import schedule
;
; def job():
; print("Running scheduled task...")
;
; schedule.every(10).minutes.do(job)
; schedule.every().hour.do(job)
; schedule.every().day.at("10:30").do(job)
;
; while True:
; schedule.run_pending()
; time.sleep(1)
# WordPress Debug Plugins
# Install with: wp plugin install <plugin-slug> --activate
# Query Monitor - Database queries, hooks, conditionals
query-monitor
# Debug Bar - Debug information in admin bar
debug-bar
# Log Deprecated Notices - Track deprecated functions
log-deprecated-notices
# Optional plugins:
# debug-bar-console - PHP console in debug bar
# debug-bar-cron - Cron information
# debug-bar-transients - Transient information
<?php
/**
* WordPress Docker Configuration
* Optimized for local development
*/
// Database settings from environment
define('DB_NAME', getenv('WORDPRESS_DB_NAME') ?: 'wordpress');
define('DB_USER', getenv('WORDPRESS_DB_USER') ?: 'wordpress');
define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD') ?: 'wordpress');
define('DB_HOST', getenv('WORDPRESS_DB_HOST') ?: 'db');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
// Authentication Keys and Salts
// Generate at: https://api.wordpress.org/secret-key/1.1/salt/
define('AUTH_KEY', 'put-your-unique-phrase-here');
define('SECURE_AUTH_KEY', 'put-your-unique-phrase-here');
define('LOGGED_IN_KEY', 'put-your-unique-phrase-here');
define('NONCE_KEY', 'put-your-unique-phrase-here');
define('AUTH_SALT', 'put-your-unique-phrase-here');
define('SECURE_AUTH_SALT', 'put-your-unique-phrase-here');
define('LOGGED_IN_SALT', 'put-your-unique-phrase-here');
define('NONCE_SALT', 'put-your-unique-phrase-here');
$table_prefix = 'wp_';
// ============================================
// Development Settings
// ============================================
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', true);
define('SCRIPT_DEBUG', true);
define('SAVEQUERIES', true);
// Memory
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
// Disable auto-updates in Docker
define('AUTOMATIC_UPDATER_DISABLED', true);
define('WP_AUTO_UPDATE_CORE', false);
// Redis Object Cache (if using Redis)
// define('WP_REDIS_HOST', 'redis');
// define('WP_REDIS_PORT', 6379);
// For production, change these:
// define('WP_DEBUG', false);
// define('WP_DEBUG_LOG', false);
if (!defined('ABSPATH')) {
define('ABSPATH', __DIR__ . '/');
}
require_once ABSPATH . 'wp-settings.php';
CMS Configuration Guide
Detailed setup instructions for WordPress, Drupal, and Joomla.
WordPress Setup
Docker Compose Service
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
depends_on:
- db
- redis
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: ${DB_DATABASE:-wordpress}
WORDPRESS_DB_USER: ${DB_USERNAME:-wordpress}
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-wordpress}Dockerfile
FROM php:8.2-fpm
# Install WordPress required extensions
RUN apt-get update && apt-get install -y \
libpng-dev libjpeg-dev libfreetype6-dev \
libzip-dev libicu-dev libxml2-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd mysqli pdo_mysql zip intl xml exif
# Install ImageMagick (optional but recommended)
RUN apt-get install -y libmagickwand-dev \
&& pecl install imagick \
&& docker-php-ext-enable imagick
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Opcache for development
RUN docker-php-ext-install opcache
COPY docker/php/opcache-dev.ini /usr/local/etc/php/conf.d/opcache.ini
# Install WP-CLI
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
&& chmod +x wp-cli.phar \
&& mv wp-cli.phar /usr/local/bin/wp
WORKDIR /var/www/htmlwp-config.php for Docker
<?php
// Database settings
define('DB_NAME', getenv('WORDPRESS_DB_NAME') ?: 'wordpress');
define('DB_USER', getenv('WORDPRESS_DB_USER') ?: 'wordpress');
define('DB_PASSWORD', getenv('WORDPRESS_DB_PASSWORD') ?: 'wordpress');
define('DB_HOST', getenv('WORDPRESS_DB_HOST') ?: 'db');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
// Authentication Keys and Salts
// Generate at: https://api.wordpress.org/secret-key/1.1/salt/
define('AUTH_KEY', 'put-your-unique-phrase-here');
define('SECURE_AUTH_KEY', 'put-your-unique-phrase-here');
define('LOGGED_IN_KEY', 'put-your-unique-phrase-here');
define('NONCE_KEY', 'put-your-unique-phrase-here');
define('AUTH_SALT', 'put-your-unique-phrase-here');
define('SECURE_AUTH_SALT', 'put-your-unique-phrase-here');
define('LOGGED_IN_SALT', 'put-your-unique-phrase-here');
define('NONCE_SALT', 'put-your-unique-phrase-here');
$table_prefix = 'wp_';
// ============================================
// Development Settings
// ============================================
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', true);
define('SCRIPT_DEBUG', true);
define('SAVEQUERIES', true);
// Memory
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
// Disable auto-updates in Docker
define('AUTOMATIC_UPDATER_DISABLED', true);
define('WP_AUTO_UPDATE_CORE', false);
// File editing in admin (optional, disable for security)
// define('DISALLOW_FILE_EDIT', true);
// ============================================
// Redis Object Cache (if using Redis)
// ============================================
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
// define('WP_REDIS_PASSWORD', 'secret');
define('WP_REDIS_DATABASE', 0);
// For production, add:
// define('WP_DEBUG', false);
// define('WP_DEBUG_LOG', false);
// define('WP_DEBUG_DISPLAY', false);
if (!defined('ABSPATH')) {
define('ABSPATH', __DIR__ . '/');
}
require_once ABSPATH . 'wp-settings.php';Debug Plugins
Install these plugins for development:
1. Query Monitor - Database queries, hooks, conditionals
wp plugin install query-monitor --activate2. Debug Bar - Debug information in admin bar
wp plugin install debug-bar --activate3. Log Deprecated Notices - Track deprecated functions
wp plugin install log-deprecated-notices --activateNginx Configuration for WordPress
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php;
client_max_body_size 100M;
# WordPress permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP handling
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
# Deny access to sensitive files
location ~ /\.ht {
deny all;
}
location = /wp-config.php {
deny all;
}
# Static file caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}Drupal Setup
Docker Compose Service
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
depends_on:
- db
environment:
DRUPAL_DB_HOST: db
DRUPAL_DB_NAME: ${DB_DATABASE:-drupal}
DRUPAL_DB_USER: ${DB_USERNAME:-drupal}
DRUPAL_DB_PASSWORD: ${DB_PASSWORD:-drupal}Dockerfile
FROM php:8.2-fpm
# Install Drupal required extensions
RUN apt-get update && apt-get install -y \
libpng-dev libjpeg-dev libfreetype6-dev \
libzip-dev libicu-dev libxml2-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd pdo_mysql zip intl xml opcache
# Install Drush globally
RUN curl -OL https://github.com/drush-ops/drush-launcher/releases/latest/download/drush.phar \
&& chmod +x drush.phar \
&& mv drush.phar /usr/local/bin/drush
WORKDIR /var/www/htmlsettings.local.php for Docker
Create sites/default/settings.local.php:
<?php
// Database configuration
$databases['default']['default'] = [
'database' => getenv('DRUPAL_DB_NAME') ?: 'drupal',
'username' => getenv('DRUPAL_DB_USER') ?: 'drupal',
'password' => getenv('DRUPAL_DB_PASSWORD') ?: 'drupal',
'host' => getenv('DRUPAL_DB_HOST') ?: 'db',
'port' => '3306',
'driver' => 'mysql',
'prefix' => '',
'collation' => 'utf8mb4_general_ci',
];
// Development settings
$settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';
$config['system.logging']['error_level'] = 'verbose';
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;
// Disable caching for development
$settings['cache']['bins']['render'] = 'cache.backend.null';
$settings['cache']['bins']['page'] = 'cache.backend.null';
$settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';
// Trusted host patterns (adjust for your domain)
$settings['trusted_host_patterns'] = [
'^localhost$',
'^127\.0\.0\.1$',
'^.+\.localhost$',
];development.services.yml
Create sites/development.services.yml:
parameters:
http.response.debug_cacheability_headers: true
twig.config:
debug: true
auto_reload: true
cache: false
services:
cache.backend.null:
class: Drupal\Core\Cache\NullBackendFactoryDrush Commands
# Clear cache
drush cr
# Run database updates
drush updb
# Install a module
drush en module_name
# Generate one-time login link
drush uliJoomla Setup
Docker Compose Service
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
depends_on:
- db
environment:
JOOMLA_DB_HOST: db
JOOMLA_DB_NAME: ${DB_DATABASE:-joomla}
JOOMLA_DB_USER: ${DB_USERNAME:-joomla}
JOOMLA_DB_PASSWORD: ${DB_PASSWORD:-joomla}Dockerfile
FROM php:8.2-fpm
# Install Joomla required extensions
RUN apt-get update && apt-get install -y \
libpng-dev libjpeg-dev libfreetype6-dev \
libzip-dev libicu-dev libxml2-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd mysqli pdo_mysql zip intl xml opcache
WORKDIR /var/www/htmlconfiguration.php for Docker
<?php
class JConfig {
// Database
public $dbtype = 'mysqli';
public $host = 'db';
public $user = 'joomla';
public $password = 'joomla';
public $db = 'joomla';
public $dbprefix = 'jos_';
public $dbencryption = 0;
public $dbsslverifyservercert = false;
public $dbsslkey = '';
public $dbsslcert = '';
public $dbsslca = '';
public $dbsslcipher = '';
// Site
public $sitename = 'Joomla Development';
public $secret = 'change-this-secret-key';
// Debug
public $debug = true;
public $debug_lang = true;
// Error reporting
public $error_reporting = 'maximum';
// Logging
public $log_path = '/var/www/html/administrator/logs';
public $tmp_path = '/var/www/html/tmp';
// Cache
public $caching = 0;
public $cache_handler = 'file';
public $cachetime = 15;
public $cache_platformprefix = false;
// Session
public $session_handler = 'database';
public $lifetime = 15;
// Mail (use Mailpit)
public $mailer = 'smtp';
public $mailfrom = 'admin@localhost';
public $fromname = 'Joomla';
public $sendmail = '/usr/sbin/sendmail';
public $smtpauth = false;
public $smtpuser = '';
public $smtppass = '';
public $smtphost = 'mailpit';
public $smtpsecure = 'none';
public $smtpport = 1025;
}Nginx Configuration for Joomla
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html;
client_max_body_size 100M;
# Joomla SEF URLs
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP handling
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to sensitive files
location ~ /\.ht {
deny all;
}
location = /configuration.php {
deny all;
}
location ~ ^/administrator/logs/ {
deny all;
}
}Common CMS Tips
File Permissions
# Inside container, set proper permissions
chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
# Writable directories
chmod -R 775 /var/www/html/wp-content/uploads # WordPress
chmod -R 775 /var/www/html/sites/default/files # Drupal
chmod -R 775 /var/www/html/images # Joomla
chmod -R 775 /var/www/html/tmp # JoomlaDatabase Import
# MySQL/MariaDB
docker compose exec db mysql -u root -p database_name < dump.sql
# Or using the app container
docker compose exec app mysql -h db -u root -p database_name < dump.sqlWP-CLI in Docker
# Run WP-CLI commands
docker compose exec app wp plugin list
docker compose exec app wp core update
docker compose exec app wp cache flush
# Import database
docker compose exec app wp db import dump.sqlHealth Check Patterns
Service verification patterns for all supported stacks.
Overview
Health checks run automatically after docker compose up to verify all services are working correctly.
Database Checks
MySQL/MariaDB
Connection check:
docker compose exec db mysqladmin ping -h localhost -u root --silentExpected output: mysqld is alive
CRUD test:
-- Create test table
CREATE TABLE IF NOT EXISTS _health_check_test (
id INT AUTO_INCREMENT PRIMARY KEY,
value VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Insert
INSERT INTO _health_check_test (value) VALUES ('test');
-- Update
UPDATE _health_check_test SET value = 'updated' WHERE value = 'test';
-- Select (verify)
SELECT COUNT(*) FROM _health_check_test WHERE value = 'updated';
-- Expected: 1
-- Delete
DELETE FROM _health_check_test WHERE value = 'updated';
-- Cleanup
DROP TABLE IF EXISTS _health_check_test;PostgreSQL
Connection check:
docker compose exec db pg_isready -U postgresExpected output: localhost:5432 - accepting connections
CRUD test:
-- Create test table
CREATE TABLE IF NOT EXISTS _health_check_test (
id SERIAL PRIMARY KEY,
value VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Insert
INSERT INTO _health_check_test (value) VALUES ('test');
-- Update
UPDATE _health_check_test SET value = 'updated' WHERE value = 'test';
-- Select (verify)
SELECT COUNT(*) FROM _health_check_test WHERE value = 'updated';
-- Expected: 1
-- Delete
DELETE FROM _health_check_test WHERE value = 'updated';
-- Cleanup
DROP TABLE IF EXISTS _health_check_test;Web Server Checks
Nginx
HTTP request:
curl -s -o /dev/null -w '%{http_code}' http://localhost:8080Expected: 200, 301, or 302
Headers check:
curl -I http://localhost:8080Check configuration:
docker compose exec nginx nginx -tRedis Checks
Ping test:
docker compose exec redis redis-cli pingExpected: PONG
Set/Get test:
docker compose exec redis redis-cli SET health_check test
docker compose exec redis redis-cli GET health_check
docker compose exec redis redis-cli DEL health_checkMemory info:
docker compose exec redis redis-cli INFO memory | grep used_memory_humanEmail Service Checks
Mailpit
Web UI check:
curl -s -o /dev/null -w '%{http_code}' http://localhost:8025Expected: 200
SMTP connection:
nc -z localhost 1025 && echo "SMTP OK" || echo "SMTP FAILED"API check:
curl -s http://localhost:8025/api/v1/messages | head -c 100MailHog
Web UI check:
curl -s -o /dev/null -w '%{http_code}' http://localhost:8025API check:
curl -s http://localhost:8025/api/v2/messages | head -c 100Application Checks
Laravel
Artisan check:
docker compose exec app php artisan --versionExpected: Laravel Framework X.X.X
Environment check:
docker compose exec app php artisan aboutDatabase connection:
docker compose exec app php artisan db:showCache check:
docker compose exec app php artisan cache:clearQueue check (if enabled):
docker compose exec app php artisan queue:work --onceQueue and scheduler discovery:
# Queue driver and failed jobs
docker compose exec app sh -lc "grep -E '^QUEUE_CONNECTION=' .env .env.docker 2>/dev/null || true"
docker compose exec app php artisan queue:failed 2>/dev/null || true
# Scheduled commands
docker compose exec app php artisan schedule:list --no-interactionIf schedule:list has no real tasks, the scheduler service can still be valid infrastructure but should be documented as idle. Do not report "scheduler support" as "scheduled jobs implemented" unless actual scheduled commands exist.
WordPress
WP-CLI version:
docker compose exec app wp --versionCore version:
docker compose exec app wp core versionDatabase check:
docker compose exec app wp db checkCore checksums:
docker compose exec app wp core verify-checksumsPlugin list:
docker compose exec app wp plugin listDrupal
Drush status:
docker compose exec app drush statusDatabase check:
docker compose exec app drush sql:query "SELECT 1"Cache rebuild:
docker compose exec app drush crDjango
Check command:
docker compose exec app python manage.py checkExpected: System check identified no issues
Database check:
docker compose exec app python manage.py dbshell -c "SELECT 1"Migration status:
docker compose exec app python manage.py showmigrationsFastAPI
Health endpoint:
curl -s http://localhost:8000/health
# or
curl -s http://localhost:8000/docsOpenAPI docs:
curl -s -o /dev/null -w '%{http_code}' http://localhost:8000/docsExpected: 200
Node.js/Express
HTTP check:
curl -s -o /dev/null -w '%{http_code}' http://localhost:3000
# or health endpoint
curl -s http://localhost:3000/healthNode version:
docker compose exec app node -vQueue Worker Checks
One-Shot Dependency Installer Checks
Dependency installer services such as api-deps, composer-deps, node-deps, or pnpm-deps are expected to stop after successful installation. Treat them as healthy when they exited with code 0 and the app containers can see the dependency directories.
docker compose ps -a api-deps node-deps
docker compose logs api-deps node-deps
docker compose exec app test -d vendor
docker compose exec web test -d node_modulesIf an installer service exits non-zero, inspect lockfile compatibility, package manager availability, auth tokens for private packages, and volume mount targets before restarting app containers.
Supervisor Process
Check running processes:
docker compose exec worker supervisorctl statusExpected output:
laravel-worker:laravel-worker_00 RUNNING pid 123, uptime 0:05:00
laravel-scheduler RUNNING pid 124, uptime 0:05:00Laravel Horizon
Horizon status:
docker compose exec app php artisan horizon:statusCelery
Worker status:
docker compose exec worker celery -A myapp statusInspect active:
docker compose exec worker celery -A myapp inspect activeFull Stack Integration Test
Test Sequence
#!/bin/bash
echo "=== Full Stack Health Check ==="
# 1. Database
echo -n "Database... "
docker compose exec -T db mysqladmin ping -u root --silent && echo "OK" || echo "FAIL"
# 2. Redis
echo -n "Redis... "
docker compose exec -T redis redis-cli ping | grep -q PONG && echo "OK" || echo "FAIL"
# 3. Web Server
echo -n "Web Server... "
curl -s -o /dev/null -w '%{http_code}' http://localhost:8080 | grep -qE '200|301|302' && echo "OK" || echo "FAIL"
# 4. Application
echo -n "Application... "
docker compose exec -T app php artisan --version > /dev/null 2>&1 && echo "OK" || echo "FAIL"
# 5. Database CRUD
echo -n "Database CRUD... "
./scripts/db-test.sh > /dev/null 2>&1 && echo "OK" || echo "FAIL"
# 6. Mail Service
echo -n "Mail Service... "
curl -s -o /dev/null -w '%{http_code}' http://localhost:8025 | grep -q 200 && echo "OK" || echo "FAIL"
echo "=== Complete ==="Common Failure Scenarios
Database Connection Refused
Symptoms:
Connection refusederrorCan't connect to MySQL server
Checks:
# Is container running?
docker compose ps db
# Check logs
docker compose logs db
# Is port exposed?
docker compose port db 3306Solutions: 1. Wait longer for database to start 2. Check environment variables 3. Check healthcheck in compose file
Redis Not Ready
Symptoms:
Could not connect to RedisConnection refused
Checks:
docker compose exec redis redis-cli ping
docker compose logs redisWeb Server 502 Bad Gateway
Symptoms:
- Nginx returns 502
- Can't reach PHP-FPM
Checks:
# Is PHP-FPM running?
docker compose exec app php-fpm -t
# Check Nginx config
docker compose exec nginx nginx -t
# Check Nginx logs
docker compose logs nginxQueue Worker Not Processing
Symptoms:
- Jobs stay in queue
- Worker shows as running but not processing
Checks:
# Check supervisor status
docker compose exec worker supervisorctl status
# Check worker logs
docker compose logs worker
# Check queue driver and actual pending/failed jobs
docker compose exec app sh -lc "grep -E '^QUEUE_CONNECTION=' .env .env.docker 2>/dev/null || true"
docker compose exec app php artisan queue:failed 2>/dev/null || true
# Manually run job
docker compose exec app php artisan queue:work --onceAlso verify that the application actually defines queued jobs/listeners. A running worker with no ShouldQueue jobs or dispatched work is not a failure.
Health Check in docker-compose.yml
Adding Health Checks
services:
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
redis:
image: redis:alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
app:
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthyWaiting for Health
# Wait for all services to be healthy
docker compose up -d --waitMerge & Backup Strategy
How to handle existing Docker files safely.
Backup Protocol
Timestamped Backup Naming
Format: {filename}.backup.{YYYY-MM-DD-HHMMSS}
Examples:
docker-compose.yml.backup.2024-01-15-143022Dockerfile.backup.2024-01-15-143022
Backup Location
Backups are created in the same directory as the original file.
# Original
./docker-compose.yml
# Backup
./docker-compose.yml.backup.2024-01-15-143022Creating Backups
# Bash function to backup file
backup_file() {
local file=$1
local timestamp=$(date +%Y-%m-%d-%H%M%S)
cp "$file" "${file}.backup.${timestamp}"
echo "Backed up: ${file}.backup.${timestamp}"
}Backup Retention
Keep the last 3 backups per file. Older backups can be removed:
# List backups sorted by date
ls -la docker-compose.yml.backup.* | sort -r
# Keep only last 3
ls -t docker-compose.yml.backup.* | tail -n +4 | xargs rm -fMerge Algorithm
Service-Level Merging
The skill merges at the service level, not line-by-line:
1. Parse existing docker-compose.yml 2. Identify existing services 3. Only add NEW services 4. Preserve existing service configurations
Merge Rules
| Scenario | Action |
|---|---|
| Service exists | Keep existing, don't modify |
| Service missing | Add from template |
| Network exists | Keep existing |
| Network missing | Add from template |
| Volume exists | Keep existing |
| Volume missing | Add from template |
| Top-level config | Preserve existing |
Example Merge
Existing docker-compose.yml:
version: '3.8'
services:
app:
build: .
volumes:
- ./:/var/www
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: custom_password # User's custom configSkill wants to add:
services:
redis:
image: redis:alpine
mailpit:
image: axllent/mailpitResult after merge:
version: '3.8'
services:
app:
build: .
volumes:
- ./:/var/www
# Original config preserved
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: custom_password # Preserved!
# NEW services added
redis:
image: redis:alpine
mailpit:
image: axllent/mailpitEnvironment Variable Preservation
Always preserve user's environment variables:
# Before merge - user has custom vars
db:
environment:
MYSQL_ROOT_PASSWORD: my_secret_pass
CUSTOM_VAR: my_value
# After merge - variables preserved
db:
environment:
MYSQL_ROOT_PASSWORD: my_secret_pass
CUSTOM_VAR: my_value
# No new vars added to existing serviceVolume Preservation
# User's custom volumes - preserved
services:
app:
volumes:
- ./custom/path:/var/www
- ~/.ssh:/root/.ssh:ro # Custom mount
# Not replaced with template defaultsNetwork Preservation
# User's custom network
networks:
my_custom_network:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
# Preserved, not replacedConflict Resolution
Show Diff Before Applying
Always show the user what will change:
services:
app:
# existing config unchanged
+
+ redis:
+ image: redis:alpine
+ volumes:
+ - redis_data:/data
+
+ mailpit:
+ image: axllent/mailpit
+ ports:
+ - "1025:1025"
+ - "8025:8025"
+
volumes:
+ redis_data:User Options
Present these options when existing files found:
I found existing Docker files. How should I proceed?
1. Merge (add new services, preserve your settings)
- Your customizations will be kept
- Only NEW services will be added
- Backup will be created first
2. Replace (backup existing, generate fresh)
- Existing files will be backed up
- New files generated from templates
- You'll need to re-apply customizations
3. Cancel (let me review first)
- No changes will be made
- You can review files manuallyResolving Specific Conflicts
Port Conflict:
Service 'nginx' already uses port 8080.
New service 'proxy' also wants port 8080.
Options:
1. Use different port for 'proxy' (8081)
2. Keep existing 'nginx' port, skip 'proxy'
3. Cancel and resolve manuallyService Name Conflict:
Template has 'app' service but one already exists.
Options:
1. Keep existing 'app' configuration
2. Rename new service to 'app_new'
3. Cancel and resolve manuallyRollback Procedure
Manual Rollback
# Find backup
ls -la *.backup.*
# Restore from backup
cp docker-compose.yml.backup.2024-01-15-143022 docker-compose.yml
# Verify
docker compose configAutomated Rollback Check
# After generating new files, verify they're valid
if ! docker compose config > /dev/null 2>&1; then
echo "Error: Generated config is invalid"
echo "Rolling back..."
cp docker-compose.yml.backup.* docker-compose.yml
exit 1
fiVerification After Merge
1. Syntax check:
docker compose config2. Service check:
docker compose config --services3. Test containers start:
docker compose up -d
docker compose psBest Practices
Always Backup First
# Before any modification
backup_file docker-compose.yml
backup_file Dockerfile
backup_file .envValidate Before Applying
# Check YAML syntax
docker compose config > /dev/null
# Check Dockerfile syntax
docker build --check .Use Version Control
# Commit before changes
git add docker-compose.yml Dockerfile
git commit -m "Backup before Docker skill updates"
# Easy rollback
git checkout docker-compose.ymlDocument Custom Changes
Add comments to mark customizations:
services:
db:
environment:
# CUSTOM: Using legacy password format
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
# CUSTOM: Company-specific variable
COMPANY_CODE: ACMETech Stack Detection Reference
This document describes how to detect various tech stacks automatically before using AI analysis.
Detection Priority
1. Run scripts/detect-stack.sh first (saves AI tokens) 2. If detection fails or user disagrees, use AI analysis 3. If stack is unsupported, warn user and proceed with generic config
PHP Detection
Laravel
Primary indicators:
composer.jsonexists AND containslaravel/frameworkartisanfile exists in project root
Version detection:
# From composer.json
grep '"laravel/framework"' composer.json | grep -oE '[0-9]+\.[0-9]+'Additional detection:
config/app.php- Laravel configurationroutes/web.php- Laravel routesapp/Http/Kernel.php- HTTP kernel
Laravel Sail detection:
docker-compose.ymlwithsailreferences.envwithSAIL_variables- Warn user to avoid conflicts with existing Sail setup
WordPress
Primary indicators:
wp-config.phpexistswp-content/directory existswp-includes/directory exists
Version detection:
# From wp-includes/version.php
grep '$wp_version' wp-includes/version.php | grep -oE '[0-9]+\.[0-9]+'Theme/Plugin detection:
wp-content/themes/- active themewp-content/plugins/- installed plugins
Drupal
Primary indicators:
core/directory with Drupal coresites/default/directorycore/lib/Drupal.phpexists
Version detection:
# From core/lib/Drupal.php
grep 'const VERSION' core/lib/Drupal.php | grep -oE '[0-9]+\.[0-9]+'Drush detection:
vendor/bin/drushexistsdrush/directory in project
Joomla
Primary indicators:
configuration.phpexistsadministrator/directory existslibraries/src/orlibraries/joomla/exists
Version detection:
# From libraries/src/Version.php
grep 'MAJOR_VERSION\|MINOR_VERSION' libraries/src/Version.phpNode.js Detection
Framework Detection
Next.js:
package.jsoncontains"next"next.config.jsornext.config.tsexists
NestJS:
package.jsoncontains"@nestjs/core"nest-cli.jsonexists
Express:
package.jsoncontains"express"- Common patterns:
app.js,server.js,index.js
Fastify:
package.jsoncontains"fastify"
Version Detection
# From .nvmrc
cat .nvmrc | tr -d 'v'
# From package.json engines
grep '"node"' package.json | grep -oE '[0-9]+'Package Manager Detection
| Lock File | Package Manager |
|---|---|
pnpm-lock.yaml | pnpm |
yarn.lock | yarn |
bun.lockb | bun |
package-lock.json | npm |
Python Detection
Framework Detection
Django:
manage.pyexistsrequirements.txtorpyproject.tomlcontainsdjangosettings.pyin project
FastAPI:
requirements.txtorpyproject.tomlcontainsfastapimain.pywith FastAPI app
Flask:
requirements.txtorpyproject.tomlcontainsflaskapp.pyorapplication.py
Version Detection
# From pyproject.toml
grep 'python' pyproject.toml | grep -oE '[0-9]+\.[0-9]+'
# From runtime.txt (Heroku style)
cat runtime.txt | grep -oE '[0-9]+\.[0-9]+'
# From .python-version (pyenv)
cat .python-versionPackage Manager Detection
| File | Package Manager |
|---|---|
poetry.lock | Poetry |
Pipfile.lock | Pipenv |
requirements.txt | pip |
Database Detection
From .env File
# MySQL
grep -E 'DB_CONNECTION=mysql|DATABASE_URL.*mysql' .env
# PostgreSQL
grep -E 'DB_CONNECTION=pgsql|DATABASE_URL.*postgres' .env
# SQLite
grep -E 'DB_CONNECTION=sqlite' .envFrom Framework Config
Laravel:
config/database.php- default connection.env-DB_CONNECTION
Django:
settings.py-DATABASESdict- Look for
mysql,postgresql,psycopg
Node.js:
config/database.jsor similar.envfor connection string
Redis Detection
From .env:
grep -E 'REDIS_HOST|CACHE_DRIVER=redis|SESSION_DRIVER=redis|QUEUE_CONNECTION=redis' .envFrom package.json:
grep -E '"redis"|"ioredis"|"bull"' package.jsonFrom requirements.txt:
grep -iE '^redis|^celery|^django-redis' requirements.txtQueue Detection
Laravel:
QUEUE_CONNECTION=redisordatabasein.envapp/Jobs/directory exists
Node.js:
bullorbullmqinpackage.json
Python:
celeryin requirements
Existing Docker Detection
Check for these files:
docker-compose.ymldocker-compose.yamlDockerfile.dockerignore
If found, ask user how to proceed: 1. Merge with existing 2. Replace (backup first) 3. Cancel
Detection Output Format
{
"detected": true,
"language": "php",
"languageVersion": "8.3",
"framework": "laravel",
"frameworkVersion": "11.0",
"cms": null,
"packageManager": "composer",
"database": "mysql",
"redis": true,
"queue": true,
"existingDocker": false,
"supported": true
}Supported Stack List
| Stack | Fully Supported |
|---|---|
| Laravel | Yes |
| WordPress | Yes |
| Drupal | Yes |
| Joomla | Yes |
| Next.js | Yes |
| NestJS | Yes |
| Express | Yes |
| Django | Yes |
| FastAPI | Yes |
| Flask | Yes |
For unsupported stacks, the skill will: 1. Warn the user 2. Proceed with generic configuration 3. Encourage contribution to improve support
#!/bin/bash
# Detect existing Docker images to suggest reuse and save disk space
# Usage: ./detect-images.sh
# Output: JSON with categorized existing images (databases, PHP, Node, Redis, mail)
set -e
# Check if Docker is running
if ! docker info &>/dev/null 2>&1; then
echo '{"docker_running": false, "error": "Docker is not running or not accessible"}'
exit 0
fi
# Function to get images matching a pattern
get_images_json() {
local pattern=$1
local first=true
while IFS='|' read -r repo_tag size; do
repo=$(echo "$repo_tag" | cut -d':' -f1)
tag=$(echo "$repo_tag" | cut -d':' -f2)
if [ "$first" = true ]; then
first=false
else
echo ","
fi
echo -n " {\"repository\": \"$repo\", \"tag\": \"$tag\", \"size\": \"$size\"}"
done < <(docker images --format '{{.Repository}}:{{.Tag}}|{{.Size}}' 2>/dev/null | grep -E "^($pattern):" || true)
}
# Start JSON output
echo "{"
echo ' "docker_running": true,'
# Database images (MySQL, MariaDB, PostgreSQL)
echo ' "databases": ['
DB_OUTPUT=$(get_images_json "mysql|mariadb|postgres")
if [ -n "$DB_OUTPUT" ]; then
echo "$DB_OUTPUT"
fi
echo ""
echo " ],"
# PHP images
echo ' "php": ['
PHP_OUTPUT=$(get_images_json "php")
if [ -n "$PHP_OUTPUT" ]; then
echo "$PHP_OUTPUT"
fi
echo ""
echo " ],"
# Node.js images
echo ' "node": ['
NODE_OUTPUT=$(get_images_json "node")
if [ -n "$NODE_OUTPUT" ]; then
echo "$NODE_OUTPUT"
fi
echo ""
echo " ],"
# Python images
echo ' "python": ['
PYTHON_OUTPUT=$(get_images_json "python")
if [ -n "$PYTHON_OUTPUT" ]; then
echo "$PYTHON_OUTPUT"
fi
echo ""
echo " ],"
# Redis images
echo ' "redis": ['
REDIS_OUTPUT=$(get_images_json "redis")
if [ -n "$REDIS_OUTPUT" ]; then
echo "$REDIS_OUTPUT"
fi
echo ""
echo " ],"
# Mail testing images (Mailpit, MailHog)
echo ' "mail": ['
MAIL_OUTPUT=$(get_images_json "mailpit|mailhog|axllent/mailpit")
if [ -n "$MAIL_OUTPUT" ]; then
echo "$MAIL_OUTPUT"
fi
echo ""
echo " ],"
# Nginx images
echo ' "nginx": ['
NGINX_OUTPUT=$(get_images_json "nginx")
if [ -n "$NGINX_OUTPUT" ]; then
echo "$NGINX_OUTPUT"
fi
echo ""
echo " ]"
echo "}"