
Docker Agents Generator
- 10 installs
- 9 repo stars
- Updated July 8, 2026
- openaec-foundation/docker-claude-skill-package
Helps with devops & ci/cd tasks.
About
docker-agents-generator is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- docker-agents-generator
- DevOps & CI/CD
- AI-coding skill
Docker Agents Generator by the numbers
- 10 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,007 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/docker-claude-skill-package --skill docker-agents-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/docker-claude-skill-package ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
docker-agents-generator
Generation Workflow
Execute these steps in order when containerizing an application:
1. Gather requirements (language, framework, database, cache)
2. Select Dockerfile template (language-specific)
3. Generate Dockerfile with multi-stage build
4. Generate .dockerignore for the language
5. Generate Compose configuration (dev and/or prod)
6. Generate .env template
7. Verify completeness---
Step 1: Requirements Gathering
ALWAYS determine these before generating any files:
[ ] Language/runtime: Node.js | Python | Go | Java | Rust | .NET
[ ] Framework: Express, FastAPI, Gin, Spring Boot, Actix, ASP.NET, etc.
[ ] Target environment: development | production | both
[ ] Database: PostgreSQL | MySQL | MongoDB | Redis | none
[ ] Cache layer: Redis | Memcached | none
[ ] Message queue: RabbitMQ | Kafka | none
[ ] Reverse proxy: Nginx | Traefik | none
[ ] Needs file watch (dev): yes | no
[ ] Ports: application port(s)
[ ] Persistent data: volume requirements---
Step 2: Dockerfile Template Decision Tree
Language?
├─ Node.js
│ ├─ Static frontend only? → multi-stage with nginx (see templates)
│ └─ Server app? → node:22-bookworm-slim runtime
├─ Python
│ ├─ ML/Data Science? → python:3.12-bookworm (full image)
│ └─ Web app? → python:3.12-slim-bookworm runtime
├─ Go
│ └─ ALWAYS → scratch or alpine runtime (static binary)
├─ Java
│ ├─ Spring Boot? → eclipse-temurin:21-jre-jammy runtime
│ └─ Other? → eclipse-temurin:21-jre-jammy runtime
├─ Rust
│ └─ ALWAYS → scratch or alpine runtime (static binary)
└─ .NET
└─ ALWAYS → mcr.microsoft.com/dotnet/aspnet runtimeBase Image Selection Rules
- ALWAYS use specific version tags, NEVER
latest - ALWAYS use
-slimor-bookworm-slimvariants for runtime stages - ALWAYS use the full SDK image for build stages only
- NEVER ship compilers, SDKs, or build tools in production images
- ALWAYS include
# syntax=docker/dockerfile:1as the first line
---
Step 3: Generate Dockerfile
ALWAYS follow this structure for every generated Dockerfile:
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM <sdk-image> AS build
WORKDIR /src
# 1. Copy dependency manifests first (cache optimization)
# 2. Install dependencies with cache mounts
# 3. Copy source code
# 4. Build application
# ---- Runtime Stage ----
FROM <minimal-image> AS runtime
# 1. Create non-root user
# 2. Copy built artifacts from build stage
# 3. Set ownership and permissions
# 4. Configure health check
# 5. Switch to non-root user
# 6. Expose port
# 7. Set ENTRYPOINT and CMDSecurity Defaults (ALWAYS Apply)
# Non-root user — ALWAYS include
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
# Health check — ALWAYS include
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD <health-check-command> || exit 1
# Non-root execution — ALWAYS the last USER instruction
USER appuser
# Exec form — ALWAYS use for ENTRYPOINT
ENTRYPOINT ["/app/binary"]Language-Specific Templates
See references/templates.md for complete, buildable templates for:
- Node.js (server app + static frontend)
- Python (pip + Poetry)
- Go (static binary)
- Java (Maven + Gradle)
- Rust (cargo)
- .NET (dotnet)
---
Step 4: Generate .dockerignore
ALWAYS generate a .dockerignore file. Select patterns based on language:
Universal Patterns (ALWAYS include)
.git
.gitignore
.dockerignore
Dockerfile
docker-compose*.yml
compose*.yaml
*.md
LICENSE
.env
.env.*
*.pem
*.key
.vscode
.idea
*.swp
.DS_Store
Thumbs.dbLanguage-Specific Additions
| Language | Additional Patterns |
|---|---|
| Node.js | node_modules/, dist/, build/, .next/, coverage/, .npm/ |
| Python | __pycache__/, *.pyc, .venv/, venv/, .pytest_cache/, *.egg-info/ |
| Go | vendor/ (if not vendoring), *.test, *.out |
| Java | target/, build/, .gradle/, *.class, *.jar (built in container) |
| Rust | target/, *.rs.bk |
| .NET | bin/, obj/, *.user, *.suo, packages/ |
---
Step 5: Generate Compose Configuration
Compose Template Decision Tree
What stack?
├─ Web + Database
│ └─ compose.yaml with app + db + named volume
├─ Web + Database + Cache
│ └─ compose.yaml with app + db + redis + named volumes
├─ Full Stack (frontend + backend + db + cache)
│ └─ compose.yaml with frontend + backend + db + redis + networks
├─ Development only
│ └─ compose.yaml with watch, bind mounts, debug ports
└─ Production only
└─ compose.yaml with resource limits, restart policy, no bind mountsCompose Generation Rules
- NEVER include the
version:field (deprecated) - ALWAYS use
depends_onwithcondition: service_healthy - ALWAYS define health checks for database and cache services
- ALWAYS use named volumes for persistent data
- ALWAYS use
env_fileinstead of hardcoded environment values - ALWAYS bind ports to
127.0.0.1in development configurations - ALWAYS include resource limits in production configurations
- ALWAYS use
restart: unless-stoppedin production - NEVER use
container_namefor services that may need scaling
See references/compose-templates.md for complete templates.
---
Step 6: Generate .env Template
ALWAYS generate a .env.example file alongside Compose configurations:
# Application
APP_PORT=3000
NODE_ENV=production
# Database
POSTGRES_USER=app
POSTGRES_PASSWORD=changeme
POSTGRES_DB=appdb
# Redis (if applicable)
REDIS_PASSWORD=changeme
# Secrets — NEVER commit actual values
# Copy this file to .env and fill in real valuesRules
- ALWAYS include placeholder values, NEVER real secrets
- ALWAYS add a comment warning not to commit
.env - ALWAYS name the template
.env.example(not.env) - ALWAYS reference it from Compose via
env_file: .env
---
Step 7: Verification Checklist
After generating all files, verify:
[ ] Dockerfile: starts with # syntax=docker/dockerfile:1
[ ] Dockerfile: uses multi-stage build (build + runtime stages)
[ ] Dockerfile: dependency manifests copied before source code
[ ] Dockerfile: uses cache mounts for package managers
[ ] Dockerfile: non-root user created and activated
[ ] Dockerfile: HEALTHCHECK instruction present
[ ] Dockerfile: ENTRYPOINT uses exec form
[ ] Dockerfile: no secrets in ENV or ARG
[ ] Dockerfile: pinned base image versions (no :latest)
[ ] .dockerignore: exists and covers language-specific patterns
[ ] Compose: no version: field
[ ] Compose: depends_on uses condition: service_healthy
[ ] Compose: all databases have healthcheck defined
[ ] Compose: named volumes for persistent data
[ ] Compose: env_file used instead of hardcoded secrets
[ ] Compose: resource limits set (production configs)
[ ] .env.example: exists with placeholder values
[ ] .env.example: no real secrets committed---
Common Generation Patterns
Development Compose with Watch
services:
app:
build:
context: .
target: build
ports:
- "127.0.0.1:3000:3000"
env_file: .env
develop:
watch:
- action: sync
path: ./src
target: /src/src
ignore:
- node_modules/
- action: rebuild
path: package.jsonProduction Compose Additions
services:
app:
build:
context: .
target: runtime
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128MDatabase Health Checks (Copy-Paste Ready)
| Database | Health Check |
|---|---|
| PostgreSQL | ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-postgres}"] |
| MySQL | ["CMD", "mysqladmin", "ping", "-h", "localhost"] |
| MongoDB | ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] |
| Redis | ["CMD", "redis-cli", "ping"] |
---
Anti-Patterns to Avoid
See references/anti-patterns.md for the complete list. Critical ones:
- NEVER generate a Dockerfile without multi-stage builds
- NEVER generate Compose without health checks on databases
- NEVER hardcode secrets in Dockerfiles or Compose files
- NEVER omit .dockerignore when generating Docker infrastructure
- NEVER use
ADDwhenCOPYsuffices - NEVER run containers as root in generated configurations
---
Reference Links
- references/templates.md -- Dockerfile templates per language
- references/compose-templates.md -- Compose templates for common stacks
- references/anti-patterns.md -- Generation mistakes to avoid
Official Sources
- https://docs.docker.com/reference/dockerfile/
- https://docs.docker.com/build/building/best-practices/
- https://docs.docker.com/build/building/multi-stage/
- https://docs.docker.com/compose/compose-file/
- https://docs.docker.com/compose/compose-file/05-services/
Generation Anti-Patterns
Mistakes to avoid when generating Dockerfiles and Compose configurations. Each anti-pattern includes the wrong approach, why it fails, and the correct alternative.
---
Dockerfile Anti-Patterns
AP-001: Single-Stage Dockerfile
Wrong:
FROM node:22
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
CMD ["node", "dist/index.js"]Why it fails: The final image contains the full Node.js SDK, all dev dependencies, source code, and build tools. Image size can exceed 1 GB.
Correct: ALWAYS use multi-stage builds. Build in one stage, copy only artifacts to a minimal runtime stage.
---
AP-002: Running as Root
Wrong:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]Why it fails: Container runs as root by default. A compromised application has full root access inside the container, and potentially to mounted volumes.
Correct: ALWAYS create a non-root user and switch to it before CMD/ENTRYPOINT.
---
AP-003: No Health Check
Wrong:
FROM node:22-slim
WORKDIR /app
COPY . .
CMD ["node", "index.js"]Why it fails: Docker and Compose cannot determine if the application is actually healthy. depends_on: condition: service_healthy will not work. Orchestrators cannot detect unresponsive containers.
Correct: ALWAYS include a HEALTHCHECK instruction that tests the application's actual health endpoint.
---
AP-004: Copying Everything Before Installing Dependencies
Wrong:
FROM node:22-slim AS build
WORKDIR /src
COPY . .
RUN npm ci
RUN npm run buildWhy it fails: Any source code change invalidates the npm ci cache. Dependencies are reinstalled on every build, even when package.json has not changed.
Correct: ALWAYS copy dependency manifests first, install dependencies, then copy source code.
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build---
AP-005: No Cache Mounts
Wrong:
RUN pip install -r requirements.txtWhy it fails: Package manager cache is discarded after each build. Rebuilds download all packages from scratch every time.
Correct: ALWAYS use --mount=type=cache for package manager caches.
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt---
AP-006: Using latest Tag
Wrong:
FROM node:latest
FROM python:latest
FROM golang:latestWhy it fails: Non-deterministic builds. The base image changes silently, potentially breaking the application. Two developers building the same Dockerfile may get different results.
Correct: ALWAYS pin to a specific version tag.
FROM node:22-bookworm-slim
FROM python:3.12-slim-bookworm
FROM golang:1.22-alpine---
AP-007: Secrets in ENV or ARG
Wrong:
ENV API_KEY=sk-1234567890
ARG DATABASE_PASSWORD=secret123
RUN some-command --password=$DATABASE_PASSWORDWhy it fails: ENV values persist in the final image and are visible via docker inspect. ARG values are visible in docker history. Both are baked into image layers permanently.
Correct: ALWAYS use secret mounts for sensitive data.
RUN --mount=type=secret,id=api_key \
cat /run/secrets/api_key | some-command---
AP-008: Shell-Form ENTRYPOINT
Wrong:
ENTRYPOINT /usr/bin/myapp --config /etc/config.yamlWhy it fails: Shell form wraps the command in /bin/sh -c, making /bin/sh PID 1 instead of the application. SIGTERM is not forwarded to the application, preventing graceful shutdown.
Correct: ALWAYS use exec form for ENTRYPOINT.
ENTRYPOINT ["/usr/bin/myapp"]
CMD ["--config", "/etc/config.yaml"]---
AP-009: Missing .dockerignore
Wrong: No .dockerignore file in the project.
Why it fails: The entire project directory is sent as build context, including node_modules/ (500+ MB), .git/ (entire history), test data, IDE configs, and potentially secret files.
Correct: ALWAYS generate a .dockerignore file with language-appropriate patterns.
---
AP-010: Using ADD Instead of COPY
Wrong:
ADD app.js /app/
ADD config.json /app/Why it fails: ADD has implicit behaviors (auto-extracting tarballs, downloading URLs) that make the Dockerfile less predictable.
Correct: ALWAYS use COPY for local file operations. Only use ADD when you specifically need tar extraction, URL download, or Git clone.
---
AP-011: Missing syntax Directive
Wrong:
FROM node:22-slim AS build
RUN --mount=type=cache,target=/root/.npm npm ciWhy it fails: Without # syntax=docker/dockerfile:1, BuildKit features like --mount, heredocs, and --chmod on COPY may not be available or may behave inconsistently.
Correct: ALWAYS include # syntax=docker/dockerfile:1 as the very first line.
---
AP-012: Separate apt-get update and install
Wrong:
RUN apt-get update
RUN apt-get install -y curlWhy it fails: The apt-get update layer gets cached. When adding new packages later, the cached update layer may reference stale package indexes, causing install failures.
Correct: ALWAYS combine update and install in one RUN, and clean up after.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*---
Compose Anti-Patterns
AP-013: Using version: Field
Wrong:
version: "3.8"
services:
web:
image: nginxWhy it fails: The version field is deprecated and ignored by modern Compose. It adds confusion and provides no benefit.
Correct: NEVER include the version: field.
---
AP-014: depends_on Without Health Check
Wrong:
services:
app:
depends_on:
- db
db:
image: postgresWhy it fails: Without a health check, depends_on only waits for the container to start, not for PostgreSQL to be ready to accept connections. The application may crash on startup.
Correct: ALWAYS use condition: service_healthy and define health checks.
services:
app:
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5---
AP-015: Hardcoded Secrets in Compose
Wrong:
services:
db:
environment:
POSTGRES_PASSWORD: "my-super-secret-password"Why it fails: Secrets are committed to version control in plain text. Anyone with repository access can read them.
Correct: ALWAYS use env_file with .env (gitignored) or variable interpolation with ${VAR:?error}.
services:
db:
env_file: .env
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Database password is required}---
AP-016: Anonymous Volumes
Wrong:
services:
db:
image: postgres
volumes:
- /var/lib/postgresql/dataWhy it fails: Anonymous volumes are recreated on docker compose down. All database data is lost.
Correct: ALWAYS use named volumes for persistent data.
services:
db:
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:---
AP-017: No Resource Limits in Production
Wrong:
services:
app:
restart: alwaysWhy it fails: A crashing container with restart: always and no resource limits can consume all system CPU and memory in a restart loop, affecting all other containers on the host.
Correct: ALWAYS set resource limits in production configurations.
services:
app:
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M---
AP-018: Exposing Ports to All Interfaces
Wrong:
ports:
- "5432:5432"
- "6379:6379"Why it fails: Database and cache ports are exposed to all network interfaces, making them accessible from outside the host. This is a significant security risk.
Correct: ALWAYS bind development ports to 127.0.0.1. In production, do NOT expose database ports at all.
# Development only
ports:
- "127.0.0.1:5432:5432"---
AP-019: Using container_name for Scalable Services
Wrong:
services:
web:
image: nginx
container_name: my-nginxWhy it fails: Container names must be unique. Setting container_name prevents docker compose up --scale web=3.
Correct: NEVER use container_name for services that may need scaling. Let Compose manage container names.
---
AP-020: No Log Rotation
Wrong:
services:
app:
image: myapp
restart: alwaysWhy it fails: Default json-file logging has no size limit. A busy application can fill the disk with log data, crashing the entire host.
Correct: ALWAYS configure log rotation in production.
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"---
Summary Checklist
Before delivering generated Docker infrastructure, verify NONE of these anti-patterns are present:
| ID | Anti-Pattern | Check |
|---|---|---|
| AP-001 | Single-stage Dockerfile | Multi-stage build present? |
| AP-002 | Running as root | Non-root USER set? |
| AP-003 | No health check | HEALTHCHECK instruction present? |
| AP-004 | COPY before dependencies | Manifests copied first? |
| AP-005 | No cache mounts | --mount=type=cache used? |
| AP-006 | latest tag | Pinned version tags? |
| AP-007 | Secrets in ENV/ARG | No secrets in layers? |
| AP-008 | Shell-form ENTRYPOINT | Exec form used? |
| AP-009 | Missing .dockerignore | .dockerignore exists? |
| AP-010 | ADD instead of COPY | COPY used for local files? |
| AP-011 | Missing syntax directive | First line is # syntax=...? |
| AP-012 | Separate apt update/install | Combined in one RUN? |
| AP-013 | version: field | No version: in Compose? |
| AP-014 | depends_on without health | condition: service_healthy? |
| AP-015 | Hardcoded secrets | env_file or interpolation? |
| AP-016 | Anonymous volumes | Named volumes declared? |
| AP-017 | No resource limits | deploy.resources set? |
| AP-018 | Ports on all interfaces | 127.0.0.1 binding in dev? |
| AP-019 | container_name on scalable | No container_name? |
| AP-020 | No log rotation | Logging configured? |
Docker Compose Templates for Common Stacks
Complete, valid Compose templates for common application architectures. Every template follows best practices: health checks, named volumes, env_file, depends_on conditions, and no deprecated version: field.
---
Web + PostgreSQL
services:
app:
build:
context: .
target: runtime
ports:
- "127.0.0.1:3000:3000"
env_file: .env
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
env_file: .env
environment:
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Database password is required}
POSTGRES_DB: ${POSTGRES_DB:-appdb}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-app}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
volumes:
db-data:---
Web + MySQL
services:
app:
build:
context: .
target: runtime
ports:
- "127.0.0.1:3000:3000"
env_file: .env
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: mysql:8.4
volumes:
- db-data:/var/lib/mysql
env_file: .env
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Root password is required}
MYSQL_DATABASE: ${MYSQL_DATABASE:-appdb}
MYSQL_USER: ${MYSQL_USER:-app}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?Database password is required}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
volumes:
db-data:---
Web + PostgreSQL + Redis
services:
app:
build:
context: .
target: runtime
ports:
- "127.0.0.1:3000:3000"
env_file: .env
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-appdb}
REDIS_URL: redis://:${REDIS_PASSWORD}@cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
env_file: .env
environment:
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Database password is required}
POSTGRES_DB: ${POSTGRES_DB:-appdb}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-app}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
cache:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD:?Redis password is required}
volumes:
- cache-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
db-data:
cache-data:---
Full Stack (Frontend + Backend + DB + Cache)
services:
frontend:
build:
context: ./frontend
target: runtime
ports:
- "127.0.0.1:80:80"
depends_on:
backend:
condition: service_healthy
restart: unless-stopped
networks:
- frontend
backend:
build:
context: ./backend
target: runtime
expose:
- "8080"
env_file: .env
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-appdb}
REDIS_URL: redis://:${REDIS_PASSWORD}@cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
restart: unless-stopped
networks:
- frontend
- backend
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
env_file: .env
environment:
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Database password is required}
POSTGRES_DB: ${POSTGRES_DB:-appdb}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-app}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- backend
cache:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD:?Redis password is required}
volumes:
- cache-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- backend
networks:
frontend:
backend:
volumes:
db-data:
cache-data:Network isolation: The db and cache services are on the backend network only. The frontend service cannot reach them directly — only backend bridges both networks.
---
Development Configuration
Use alongside a base compose.yaml via docker compose -f compose.yaml -f compose.dev.yaml up:
services:
app:
build:
context: .
target: build
ports:
- "127.0.0.1:3000:3000"
- "127.0.0.1:9229:9229"
env_file: .env
environment:
NODE_ENV: development
develop:
watch:
- action: sync
path: ./src
target: /src/src
ignore:
- node_modules/
- action: rebuild
path: package.json
db:
ports:
- "127.0.0.1:5432:5432"
adminer:
image: adminer:latest
ports:
- "127.0.0.1:8080:8080"
depends_on:
db:
condition: service_healthy
profiles:
- debugDevelopment Configuration Rules
- ALWAYS bind ports to
127.0.0.1to prevent external access - ALWAYS expose debug ports (9229 for Node.js, 5005 for Java, etc.)
- ALWAYS expose database ports for direct access with local tools
- ALWAYS use the
buildtarget (notruntime) for hot reload support - ALWAYS use
develop.watchfor file synchronization - ALWAYS put admin tools (Adminer, phpMyAdmin) behind a
debugprofile
---
Production Configuration
Use alongside a base compose.yaml via docker compose -f compose.yaml -f compose.prod.yaml up -d:
services:
app:
build:
context: .
target: runtime
ports:
- "3000:3000"
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
db:
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
cache:
restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64MProduction Configuration Rules
- ALWAYS set
restart: unless-stopped - ALWAYS set resource limits and reservations
- ALWAYS configure log rotation (
max-size,max-file) - NEVER expose database ports to the host
- NEVER bind ports to
127.0.0.1if the service must be publicly reachable - NEVER include admin tools or debug profiles
- NEVER use
develop.watchin production
---
Web + MongoDB
services:
app:
build:
context: .
target: runtime
ports:
- "127.0.0.1:3000:3000"
env_file: .env
environment:
MONGODB_URI: mongodb://${MONGO_USER:-app}:${MONGO_PASSWORD}@db:27017/${MONGO_DB:-appdb}?authSource=admin
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: mongo:7
volumes:
- db-data:/data/db
env_file: .env
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER:-app}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:?MongoDB password is required}
MONGO_INITDB_DATABASE: ${MONGO_DB:-appdb}
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
restart: unless-stopped
volumes:
db-data:---
.env.example Template
ALWAYS generate this alongside any Compose configuration:
# ============================================
# Application Configuration
# ============================================
# Copy this file to .env and fill in real values.
# NEVER commit the .env file to version control.
# ============================================
# Application
APP_PORT=3000
NODE_ENV=production
# PostgreSQL
POSTGRES_USER=app
POSTGRES_PASSWORD=changeme
POSTGRES_DB=appdb
# Redis (if applicable)
REDIS_PASSWORD=changeme
# MongoDB (if applicable)
MONGO_USER=app
MONGO_PASSWORD=changeme
MONGO_DB=appdb---
Template Customization Rules
1. ALWAYS remove unused services (do not include Redis if the app does not use caching) 2. ALWAYS update port numbers to match the actual application 3. ALWAYS update health check commands to match the actual health endpoint 4. ALWAYS update environment variable names to match the application's expected configuration 5. ALWAYS use ${VAR:?error} for required variables to fail fast on missing config 6. ALWAYS use ${VAR:-default} for optional variables with sensible defaults 7. NEVER include services the application does not need 8. NEVER hardcode passwords or secrets directly in Compose files
Dockerfile Templates per Language
Complete, buildable Dockerfile templates for each supported language. Every template follows the same structure: multi-stage build, cache mounts, non-root user, health check, exec-form entrypoint.
---
Node.js — Server Application
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM node:22-bookworm-slim AS build
WORKDIR /src
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
COPY . .
RUN npm run build
# ---- Runtime Stage ----
FROM node:22-bookworm-slim AS runtime
WORKDIR /app
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build --chown=appuser:appuser /src/dist ./dist
COPY --from=build --chown=appuser:appuser /src/node_modules ./node_modules
COPY --from=build --chown=appuser:appuser /src/package.json ./
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1) })" || exit 1
USER appuser
EXPOSE 3000
ENTRYPOINT ["node"]
CMD ["dist/index.js"]Node.js — Static Frontend (React, Vue, Angular)
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM node:22-bookworm-slim AS build
WORKDIR /src
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
# ---- Runtime Stage ----
FROM nginx:1.27-alpine AS runtime
RUN addgroup -S appuser && adduser -S appuser -G appuser
COPY --from=build /src/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]---
Python — pip
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM python:3.12-slim-bookworm AS build
WORKDIR /src
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-compile --prefix=/install -r requirements.txt
COPY . .
# ---- Runtime Stage ----
FROM python:3.12-slim-bookworm AS runtime
WORKDIR /app
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build /install /usr/local
COPY --from=build --chown=appuser:appuser /src .
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
USER appuser
EXPOSE 8000
ENTRYPOINT ["python"]
CMD ["-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Python — Poetry
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM python:3.12-slim-bookworm AS build
WORKDIR /src
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1
RUN --mount=type=cache,target=/root/.cache/pip \
pip install poetry
COPY pyproject.toml poetry.lock ./
RUN --mount=type=cache,target=/root/.cache/pypoetry \
poetry install --without dev --no-root
COPY . .
RUN poetry install --without dev
# ---- Runtime Stage ----
FROM python:3.12-slim-bookworm AS runtime
WORKDIR /app
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build /src/.venv ./.venv
COPY --from=build --chown=appuser:appuser /src .
ENV PATH="/app/.venv/bin:$PATH"
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
USER appuser
EXPOSE 8000
ENTRYPOINT ["python"]
CMD ["-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]---
Go
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -ldflags="-s -w" -o /bin/app ./cmd/server
# ---- Runtime Stage ----
FROM scratch AS runtime
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /bin/app /app
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/app", "healthcheck"]
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/app"]Notes:
scratchhas no shell — health check MUST use the binary itself (implement ahealthchecksubcommand) or usealpineas runtime instead.CGO_ENABLED=0produces a fully static binary.USER 65534:65534is thenobodyuser on Linux.-ldflags="-s -w"strips debug info, reducing binary size.
Go with Alpine Runtime (when shell is needed)
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS build
ARG TARGETOS TARGETARCH
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -ldflags="-s -w" -o /bin/app ./cmd/server
FROM alpine:3.21 AS runtime
RUN addgroup -S appuser && adduser -S appuser -G appuser
RUN apk --no-cache add ca-certificates wget
COPY --from=build /bin/app /usr/bin/app
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
USER appuser
EXPOSE 8080
ENTRYPOINT ["/usr/bin/app"]---
Java — Maven
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM eclipse-temurin:21-jdk-jammy AS build
WORKDIR /src
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
RUN chmod +x mvnw
RUN --mount=type=cache,target=/root/.m2 \
./mvnw dependency:go-offline
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 \
./mvnw package -DskipTests
# ---- Runtime Stage ----
FROM eclipse-temurin:21-jre-jammy AS runtime
WORKDIR /app
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build --chown=appuser:appuser /src/target/*.jar app.jar
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
USER appuser
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"]Java — Gradle
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM eclipse-temurin:21-jdk-jammy AS build
WORKDIR /src
COPY build.gradle settings.gradle gradlew ./
COPY gradle ./gradle
RUN chmod +x gradlew
RUN --mount=type=cache,target=/root/.gradle \
./gradlew dependencies --no-daemon
COPY src ./src
RUN --mount=type=cache,target=/root/.gradle \
./gradlew bootJar --no-daemon -x test
# ---- Runtime Stage ----
FROM eclipse-temurin:21-jre-jammy AS runtime
WORKDIR /app
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build --chown=appuser:appuser /src/build/libs/*.jar app.jar
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
USER appuser
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"]---
Rust
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM rust:1.77-bookworm AS build
WORKDIR /src
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs
RUN --mount=type=cache,target=/app/target/ \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/usr/local/cargo/registry/ \
cargo build --release
COPY src ./src
RUN touch src/main.rs && \
--mount=type=cache,target=/app/target/ \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/usr/local/cargo/registry/ \
cargo build --release && \
cp /app/target/release/myapp /bin/app
# ---- Runtime Stage ----
FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates wget \
&& rm -rf /var/lib/apt/lists/*
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build /bin/app /usr/bin/app
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
USER appuser
EXPOSE 8080
ENTRYPOINT ["/usr/bin/app"]Notes:
- The dummy
main.rstrick caches dependency compilation separately from source changes. - Use
debian:bookworm-slimfor Rust apps that link dynamically. For static builds (RUSTFLAGS='-C target-feature=+crt-static'), usescratch. - Replace
myappwith the actual binary name fromCargo.toml.
---
.NET
# syntax=docker/dockerfile:1
# ---- Build Stage ----
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj ./
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish --no-restore
# ---- Runtime Stage ----
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
RUN groupadd -r appuser && useradd --no-log-init -r -g appuser appuser
COPY --from=build --chown=appuser:appuser /app/publish .
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
USER appuser
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]Notes:
- Replace
MyApp.dllwith the actual assembly name. - For self-contained deployments, add
--self-contained true -r linux-x64todotnet publishand usemcr.microsoft.com/dotnet/runtime-deps:8.0as runtime base. - The .NET 8+ default port is 8080 (changed from 80 in earlier versions).
---
Template Customization Rules
When adapting these templates:
1. ALWAYS keep the # syntax=docker/dockerfile:1 directive 2. ALWAYS keep multi-stage structure (build + runtime) 3. ALWAYS keep cache mounts for the language's package manager 4. ALWAYS keep the non-root user pattern 5. ALWAYS keep the HEALTHCHECK instruction 6. ALWAYS update port numbers to match the application 7. ALWAYS update health check URLs to match the application's health endpoint 8. NEVER add secrets via ENV or ARG — use --mount=type=secret if needed 9. NEVER remove the WORKDIR instruction 10. NEVER use shell-form ENTRYPOINT