docker
When to use
Use this skill when working with Docker configuration, container setup, Dockerfile changes, or docker-compose modifications.
Do NOT use when:
- Production deployment (use
aws-infrastructure skill)
- Codespaces setup (use
devcontainer skill)
Procedure: Modify Docker setup
- Gather context — read project Docker docs in
agents/ or Docs/, check Makefile/Taskfile.yml for targets, read docker-compose.yml/compose.yaml for service layout.
- Identify scope — determine which service(s) are affected (PHP, NGINX, worker, scheduler, database).
- Inspect current state — run
docker compose ps to see running containers and their health status.
- Make the change — edit the relevant file (Dockerfile, compose file, NGINX config, Makefile target). Follow the conventions in the reference sections below.
- Rebuild affected containers —
docker compose build <service> (add --no-cache if Dockerfile base layers changed).
- Verify —
docker compose up -d, check docker compose ps for healthy status, run a smoke test (e.g., make test-quick or curl localhost).
Project architecture
Dockerfile (.docker/Dockerfile)
Multi-stage build with these targets:
| Stage |
Purpose |
base |
Alpine + PHP-FPM + system packages + extensions |
dev |
Development: Xdebug, dev tools, Composer dev deps |
pro |
Production: optimized, no dev deps, New Relic agent |
Key build args:
PHP_VERSION — extracted from Dockerfile, used by CI
COMPOSER_AUTH — private registry access (passed as secret)
CACHEBUST — weekly cache invalidation (date +%Y-%U)
COMPOSER_NO_DEV — 1 for production, 0 for dev
Dual-container architecture (PHP projects)
Some projects run two PHP-FPM containers simultaneously (fast + Xdebug):
| Container |
Purpose |
PHP-FPM mode |
{project}-php |
Fast execution, no debugger |
pm = dynamic |
{project}-php-xdebug |
Xdebug enabled, debugging |
pm = ondemand |
NGINX routes requests based on HTTP headers:
- No header → fast container
X-Xdebug-Enable: 1 or X-Debug-Session: PHPSTORM → Xdebug container
docker-compose services
Read docker-compose.yml / compose.yaml to discover the actual service names. Common patterns:
| Service type |
Description |
| PHP-FPM |
Main application server |
| PHP-FPM + Xdebug |
Debugging container |
| NGINX |
Reverse proxy |
| Queue worker |
Background job processing (e.g., Horizon) |
| Scheduler |
Cron/task scheduler |
| Database |
MariaDB / MySQL / PostgreSQL |
| Cache |
Redis / Memcached |
Conventions
Container commands
- Always execute PHP commands inside the container, never on the host.
- Use
docker compose exec -T <service> ... for non-interactive (scripts, CI).
- Use
make console for interactive shell access.
- Use
make console-xdebug for Xdebug container access.
Tooling detection
Which test runner and quality commands exist depends on the project shape.
Check for artisan in the project root before picking one:
- Laravel (
artisan present) — php artisan test, vendor/bin/phpstan analyse, vendor/bin/rector process
- Plain Composer (no
artisan) — vendor/bin/phpunit, vendor/bin/phpstan analyse, vendor/bin/rector process
Either way the command runs inside the container:
docker compose exec -T <php-service> <command>.
Image building
- Production images use
target: pro — no dev dependencies.
- Check the project's CI/CD config for target platform and registry.
- Docker Hub login may be needed for pulling base images (rate limits).
PHP extensions
Extensions are installed via mlocati/php-extension-installer:
- Check the Dockerfile for the current list.
- Add new extensions in the
base stage so they're available in all targets.
Environment files
.env is NOT baked into the Docker image.
- Production:
.env is fetched from AWS Secrets Manager at deploy time.
- Development:
.env is mounted via docker-compose volumes.
Makefile targets
Always check the Makefile for available targets before using raw docker commands:
make start # Start all containers
make stop # Stop all containers
make console # Enter PHP container (bash)
make console-xdebug # Enter Xdebug PHP container
make composer-install # Run composer install in container
make migrate # Run migrations
make migrate-and-seed # Run migrations + seed
make test # Run all tests (parallel)
Container orchestration
Environment synchronization
When the development environment is out of sync (missing containers, wrong state):
- Check status —
docker compose ps to see which services are running.
- Start missing services —
make start or docker compose up -d.
- Rebuild if needed —
docker compose build --no-cache <service> after Dockerfile changes.
- Reset state —
make migrate-and-seed after fresh container start.
Common sync issues
| Symptom |
Cause |
Fix |
| "Connection refused" |
Container not running |
make start |
| "Table not found" |
Migrations not run |
make migrate-and-seed |
| "Class not found" |
Composer not installed |
make composer-install |
| Old PHP version |
Image not rebuilt |
docker compose build <php-service> |
| Extension missing |
Dockerfile changed |
Rebuild with --no-cache |
Multi-project orchestration
When running multiple projects simultaneously:
- Check for port conflicts — each project needs unique exposed ports.
- Use Traefik (see
traefik skill) for routing by domain instead of port.
- Shared services (MariaDB, Redis) can be in a dedicated
docker-compose.shared.yml.
Security hardening checklist
When creating or reviewing Dockerfiles:
# Security pattern
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --chown=appuser:appgroup . .
USER 1001
Health check patterns
Always add health checks to long-running services:
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
In docker-compose, use condition: service_healthy for dependency ordering:
services:
app:
depends_on:
db:
condition: service_healthy
Image size optimization
| Technique |
Impact |
When |
| Multi-stage builds |
High |
Always — separate build from runtime |
| Alpine base images |
High |
When compatibility allows |
| Distroless images |
High |
Production, no shell needed |
.dockerignore |
Medium |
Always — exclude node_modules, .git, tests, docs |
Combine RUN layers |
Medium |
When installing packages + cleaning cache |
| Copy only artifacts |
Medium |
COPY --from=build only what's needed |
Build cache optimization
Use BuildKit cache mounts for package managers:
# Composer (PHP)
RUN --mount=type=cache,target=/root/.composer/cache \
composer install --no-dev --optimize-autoloader
# npm (Node.js)
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production
Layer ordering for cache efficiency:
- System packages (changes rarely)
- Dependency files (
composer.json, package.json) — changes sometimes
RUN install — cached if dependency files unchanged
- Source code (
COPY . .) — changes often, last layer
Output format
- Modified Docker configuration files (Dockerfile, docker-compose.yml)
- Updated Makefile targets if applicable
- Rebuild/restart instructions for affected containers
Auto-trigger keywords
- Docker
- docker-compose
- container
- Dockerfile
- PHP container
Known pitfalls
| Symptom |
Root cause |
Fix |
| Every build reinstalls all dependencies (builds are slow) |
COPY . . runs before the dependency install, so any source edit busts the dependency layer's cache |
Copy only the manifest + lockfile (composer.json+composer.lock / package.json+lock), install deps, THEN COPY . . |
| Image is much larger / slower to push than expected |
No .dockerignore, so .git, vendor/, node_modules/, and local env files enter the build context and image |
Add a .dockerignore excluding VCS, installed deps, build output, and secrets |
vendor/ or node_modules/ is empty inside the container even though install ran |
A bind-mount of the project directory shadows the image's installed-deps directory |
Put a named/anonymous volume over the deps dir, or don't bind-mount over it |
Files the container writes are owned by root on the host |
The container process runs as UID 0; bind-mounted writes inherit that owner |
Run as a non-root USER whose UID matches the host user, or chown on entry |
| Container exits immediately with code 0 |
The CMD process daemonizes/backgrounds, so PID 1 has nothing to keep alive |
Run the long-lived process in the foreground as PID 1 (no &, no daemonize flag) |
Gotcha
- All PHP commands (artisan, composer, phpunit) must run INSIDE the PHP container — never on the host.
- The fast container and Xdebug container share the same codebase but have different PHP configs — don't confuse them.
docker compose down -v destroys volumes including the database — use down without -v unless you mean it.
- The model forgets to use
docker compose exec -T (no TTY) when running in scripts or CI.
Do NOT
- Do NOT change the base Alpine or PHP version without checking CI compatibility.
- Do NOT add dev-only tools to the
pro stage.
- Do NOT hardcode secrets in the Dockerfile — use build args or runtime secrets.
- Do NOT change
platform without verifying AWS runner architecture.
Related
- Skill:
traefik — local reverse proxy with real domains and HTTPS
- Skill:
devcontainer — DevContainer and Codespaces setup
- Skill:
php-debugging — Xdebug dual-container architecture
- Rule:
docker-commands.md — all PHP commands run inside Docker
1---2name: docker3description: Use when working with Docker — Dockerfile edits, docker-compose services, containers, or the dual-container (fast + Xdebug) setup — even when the user just says 'my container won't start'.4---56# docker78## When to use910Use this skill when working with Docker configuration, container setup, Dockerfile changes, or docker-compose modifications.111213Do NOT use when:14- Production deployment (use `aws-infrastructure` skill)15- Codespaces setup (use `devcontainer` skill)1617## Procedure: Modify Docker setup18191. **Gather context** — read project Docker docs in `agents/` or `Docs/`, check `Makefile`/`Taskfile.yml` for targets, read `docker-compose.yml`/`compose.yaml` for service layout.202. **Identify scope** — determine which service(s) are affected (PHP, NGINX, worker, scheduler, database).213. **Inspect current state** — run `docker compose ps` to see running containers and their health status.224. **Make the change** — edit the relevant file (Dockerfile, compose file, NGINX config, Makefile target). Follow the conventions in the reference sections below.235. **Rebuild affected containers** — `docker compose build <service>` (add `--no-cache` if Dockerfile base layers changed).246. **Verify** — `docker compose up -d`, check `docker compose ps` for healthy status, run a smoke test (e.g., `make test-quick` or `curl localhost`).2526## Project architecture2728### Dockerfile (`.docker/Dockerfile`)2930Multi-stage build with these targets:3132| Stage | Purpose |33|---|---|34| `base` | Alpine + PHP-FPM + system packages + extensions |35| `dev` | Development: Xdebug, dev tools, Composer dev deps |36| `pro` | Production: optimized, no dev deps, New Relic agent |3738Key build args:39- `PHP_VERSION` — extracted from Dockerfile, used by CI40- `COMPOSER_AUTH` — private registry access (passed as secret)41- `CACHEBUST` — weekly cache invalidation (`date +%Y-%U`)42- `COMPOSER_NO_DEV` — `1` for production, `0` for dev4344### Dual-container architecture (PHP projects)4546Some projects run two PHP-FPM containers simultaneously (fast + Xdebug):4748| Container | Purpose | PHP-FPM mode |49|---|---|---|50| `{project}-php` | Fast execution, no debugger | `pm = dynamic` |51| `{project}-php-xdebug` | Xdebug enabled, debugging | `pm = ondemand` |5253NGINX routes requests based on HTTP headers:54- No header → fast container55- `X-Xdebug-Enable: 1` or `X-Debug-Session: PHPSTORM` → Xdebug container5657### docker-compose services5859Read `docker-compose.yml` / `compose.yaml` to discover the actual service names. Common patterns:6061| Service type | Description |62|---|---|63| PHP-FPM | Main application server |64| PHP-FPM + Xdebug | Debugging container |65| NGINX | Reverse proxy |66| Queue worker | Background job processing (e.g., Horizon) |67| Scheduler | Cron/task scheduler |68| Database | MariaDB / MySQL / PostgreSQL |69| Cache | Redis / Memcached |7071## Conventions7273### Container commands7475- **Always execute PHP commands inside the container**, never on the host.76- Use `docker compose exec -T <service> ...` for non-interactive (scripts, CI).77- Use `make console` for interactive shell access.78- Use `make console-xdebug` for Xdebug container access.7980### Tooling detection8182Which test runner and quality commands exist depends on the project shape.83Check for `artisan` in the project root before picking one:8485- **Laravel** (`artisan` present) — `php artisan test`, `vendor/bin/phpstan analyse`, `vendor/bin/rector process`86- **Plain Composer** (no `artisan`) — `vendor/bin/phpunit`, `vendor/bin/phpstan analyse`, `vendor/bin/rector process`8788Either way the command runs inside the container:89`docker compose exec -T <php-service> <command>`.9091### Image building9293- Production images use `target: pro` — no dev dependencies.94- Check the project's CI/CD config for target platform and registry.95- Docker Hub login may be needed for pulling base images (rate limits).9697### PHP extensions9899Extensions are installed via `mlocati/php-extension-installer`:100- Check the Dockerfile for the current list.101- Add new extensions in the `base` stage so they're available in all targets.102103### Environment files104105- `.env` is NOT baked into the Docker image.106- Production: `.env` is fetched from **AWS Secrets Manager** at deploy time.107- Development: `.env` is mounted via docker-compose volumes.108109## Makefile targets110111Always check the `Makefile` for available targets before using raw docker commands:112113```114make start # Start all containers115make stop # Stop all containers116make console # Enter PHP container (bash)117make console-xdebug # Enter Xdebug PHP container118make composer-install # Run composer install in container119make migrate # Run migrations120make migrate-and-seed # Run migrations + seed121make test # Run all tests (parallel)122```123124125## Container orchestration126127### Environment synchronization128129When the development environment is out of sync (missing containers, wrong state):1301311. **Check status** — `docker compose ps` to see which services are running.1322. **Start missing services** — `make start` or `docker compose up -d`.1333. **Rebuild if needed** — `docker compose build --no-cache <service>` after Dockerfile changes.1344. **Reset state** — `make migrate-and-seed` after fresh container start.135136### Common sync issues137138| Symptom | Cause | Fix |139|---|---|---|140| "Connection refused" | Container not running | `make start` |141| "Table not found" | Migrations not run | `make migrate-and-seed` |142| "Class not found" | Composer not installed | `make composer-install` |143| Old PHP version | Image not rebuilt | `docker compose build <php-service>` |144| Extension missing | Dockerfile changed | Rebuild with `--no-cache` |145146### Multi-project orchestration147148When running multiple projects simultaneously:149- Check for **port conflicts** — each project needs unique exposed ports.150- Use **Traefik** (see `traefik` skill) for routing by domain instead of port.151- Shared services (MariaDB, Redis) can be in a dedicated `docker-compose.shared.yml`.152153## Security hardening checklist154155When creating or reviewing Dockerfiles:156157- [ ] **Non-root user** — create user with specific UID/GID, use `USER` directive before `CMD`.158- [ ] **No secrets in layers** — never `ENV` or `COPY` secrets. Use `--mount=type=secret` (BuildKit) or runtime secrets.159- [ ] **Minimal packages** — only install what's needed. Remove package manager cache in the same `RUN` layer.160- [ ] **Read-only root filesystem** — use `--read-only` flag where possible, mount writable dirs explicitly.161- [ ] **No `latest` tag** — pin base image versions (`node:18.19-alpine`, not `node:latest`).162- [ ] **Scan images** — use `docker scout quickview` or Trivy for vulnerability scanning.163164```dockerfile165# Security pattern166RUN addgroup -g 1001 -S appgroup && \167 adduser -S appuser -u 1001 -G appgroup168COPY --chown=appuser:appgroup . .169USER 1001170```171172## Health check patterns173174Always add health checks to long-running services:175176```dockerfile177HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \178 CMD curl -f http://localhost:8080/health || exit 1179```180181In docker-compose, use `condition: service_healthy` for dependency ordering:182183```yaml184services:185 app:186 depends_on:187 db:188 condition: service_healthy189```190191## Image size optimization192193| Technique | Impact | When |194|---|---|---|195| Multi-stage builds | **High** | Always — separate build from runtime |196| Alpine base images | **High** | When compatibility allows |197| Distroless images | **High** | Production, no shell needed |198| `.dockerignore` | **Medium** | Always — exclude `node_modules`, `.git`, `tests`, docs |199| Combine `RUN` layers | **Medium** | When installing packages + cleaning cache |200| Copy only artifacts | **Medium** | `COPY --from=build` only what's needed |201202## Build cache optimization203204Use BuildKit cache mounts for package managers:205206```dockerfile207# Composer (PHP)208RUN --mount=type=cache,target=/root/.composer/cache \209 composer install --no-dev --optimize-autoloader210211# npm (Node.js)212RUN --mount=type=cache,target=/root/.npm \213 npm ci --only=production214```215216**Layer ordering for cache efficiency:**2171. System packages (changes rarely)2182. Dependency files (`composer.json`, `package.json`) — changes sometimes2193. `RUN install` — cached if dependency files unchanged2204. Source code (`COPY . .`) — changes often, last layer221222## Output format2232241. Modified Docker configuration files (Dockerfile, docker-compose.yml)2252. Updated Makefile targets if applicable2263. Rebuild/restart instructions for affected containers227228## Auto-trigger keywords229230- Docker231- docker-compose232- container233- Dockerfile234- PHP container235236## Known pitfalls237238| Symptom | Root cause | Fix |239|---|---|---|240| Every build reinstalls all dependencies (builds are slow) | `COPY . .` runs before the dependency install, so any source edit busts the dependency layer's cache | Copy only the manifest + lockfile (`composer.json`+`composer.lock` / `package.json`+lock), install deps, THEN `COPY . .` |241| Image is much larger / slower to push than expected | No `.dockerignore`, so `.git`, `vendor/`, `node_modules/`, and local env files enter the build context and image | Add a `.dockerignore` excluding VCS, installed deps, build output, and secrets |242| `vendor/` or `node_modules/` is empty inside the container even though install ran | A bind-mount of the project directory shadows the image's installed-deps directory | Put a named/anonymous volume over the deps dir, or don't bind-mount over it |243| Files the container writes are owned by `root` on the host | The container process runs as UID 0; bind-mounted writes inherit that owner | Run as a non-root `USER` whose UID matches the host user, or `chown` on entry |244| Container exits immediately with code 0 | The `CMD` process daemonizes/backgrounds, so PID 1 has nothing to keep alive | Run the long-lived process in the foreground as PID 1 (no `&`, no daemonize flag) |245246## Gotcha247248- All PHP commands (artisan, composer, phpunit) must run INSIDE the PHP container — never on the host.249- The fast container and Xdebug container share the same codebase but have different PHP configs — don't confuse them.250- `docker compose down -v` destroys volumes including the database — use `down` without `-v` unless you mean it.251- The model forgets to use `docker compose exec -T` (no TTY) when running in scripts or CI.252253## Do NOT254255- Do NOT change the base Alpine or PHP version without checking CI compatibility.256- Do NOT add dev-only tools to the `pro` stage.257- Do NOT hardcode secrets in the Dockerfile — use build args or runtime secrets.258- Do NOT change `platform` without verifying AWS runner architecture.259260## Related261262- **Skill:** `traefik` — local reverse proxy with real domains and HTTPS263- **Skill:** `devcontainer` — DevContainer and Codespaces setup264- **Skill:** `php-debugging` — Xdebug dual-container architecture265- **Rule:** `docker-commands.md` — all PHP commands run inside Docker