| name | container-projects |
| description | Guidelines for containerized projects using Docker, Docker Compose, and container orchestration. Covers Dockerfiles, multi-stage builds, security, signal handling, entrypoint scripts, and deployment workflows. |
Container-Based Projects
Auto-activate when: Working with Dockerfile, docker-compose.yml, docker-compose.yaml, .dockerignore, Kubernetes manifests (*.yaml, *.yml in k8s directories), container registries, or when user mentions Docker, containers, orchestration, or deployment workflows.
Guidelines for containerized applications using Docker, Docker Compose, and orchestration tools.
CRITICAL: Docker Compose V2 Syntax
NEVER use version: field (deprecated) or docker-compose with hyphen:
# ❌ WRONG
version: '3.8'
services:
app:
image: myapp
# ✅ CORRECT
services:
app:
image: myapp
docker compose up # ✅ CORRECT
docker-compose up # ❌ WRONG
CRITICAL: DNS Configuration
# ✅ ALWAYS use .internal for container DNS
services:
app:
environment:
- DNS_DOMAIN=.internal
# ❌ NEVER use .local (conflicts with mDNS/Bonjour)
Dockerfile Core Requirements
Base Images
- Use Alpine Linux for minimal attack surface and smaller images
- Example:
python:3.12-alpine,node:20-alpine
- Example:
- Specify version tags for reproducible builds (never use
latest) - Use official images from trusted registries
- Consider distroless images for production
- If Alpine packages unavailable, use Debian Slim-based containers
Multi-stage Builds
- Separate stages for different purposes:
base- Common dependencies and user setupdevelopment- Development tools and dependenciesproduction- Minimal runtime with only production dependencies
- Copy only necessary artifacts to final stage
- Reduces final image size and attack surface
- Order commands from least to most frequently changing
Security Checklist
- ✅ Create and use non-root users
- ✅ Set USER directive before EXPOSE and CMD
- ✅ Never include secrets in layers
- ✅ Use
.dockerignoreto exclude sensitive files - ✅ Scan images for vulnerabilities regularly
- ✅ Keep base images updated
- ✅ Run as non-root user (USER directive)
- ✅ Never hardcode secrets or commit credentials
- ✅ Validate all input, even from trusted sources
- ✅ Include health checks for orchestration
- ✅ Use Docker secrets for sensitive data in production
Project Structure Recognition
Key files: Dockerfile, docker-compose.yml, .dockerignore, .devcontainer/, Makefile, k8s/
Workflow Patterns
Before starting: Check README, Makefile, docker-compose.yml, .env files
Command hierarchy: Makefile → Project scripts → Docker commands
Layer Optimization
Reduce Layer Count
- Group RUN commands to reduce layers
- Use
&&to chain related commands - Clean up in the same layer as installation
Example: Bad vs. Good
# ❌ BAD: Multiple layers
RUN apk update
RUN apk add curl
RUN apk add git
RUN rm -rf /var/cache/apk/*
# ✅ GOOD: Single optimized layer
RUN apk update && \
apk add --no-cache \
curl \
git && \
rm -rf /var/cache/apk/*
Alpine APK Best Practices
- Use
apk add --no-cacheto avoid caching package index - Maintain alphabetical order in package lists for maintainability
- Remove cache in the same RUN command if not using
--no-cache
RUN apk add --no-cache \
bash \
curl \
git \
openssh \
vim
Cache Optimization
- Order commands from least to most frequently changing
- Copy dependency files separately before copying source code
- Use BuildKit cache mounts for package managers
# ✅ GOOD: Dependency layer cached separately
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-cache-dir -r requirements.txt
# Source code changes won't invalidate dependency cache
COPY app/ ./app/
Package Management Best Practices
Python UV (Modern Package Manager)
# Copy uv from official image
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Install dependencies with cache mount
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system --no-cache -r requirements.txt
Traditional Python Pip
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-cache-dir -r requirements.txt
Non-Root User Setup
# Create non-root user
ARG PUID=1000
ARG PGID=1000
ARG USER=appuser
RUN addgroup -g ${PGID} ${USER} && \
adduser -D -u ${PUID} -G ${USER} -s /bin/sh ${USER}
# Switch to non-root user before EXPOSE and CMD
USER ${USER}
12-Factor App Compliance
| Factor | Implementation |
|---|---|
| Configuration | Environment variables only, never hardcoded |
| Dependencies | Explicit declarations with lockfiles |
| Stateless | No local state, horizontally scalable |
| Port Binding | Self-contained, exports via port binding |
| Disposability | Fast startup/shutdown, graceful termination |
| Dev/Prod Parity | Keep environments similar |
Complete Multi-Stage Build Template
# Base stage with common setup
FROM python:3.12-alpine AS base
# Install uv for fast dependency management
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Build arguments
ARG PUID=1000
ARG PGID=1000
ARG USER=appuser
ARG WORKDIR=/app
# Create non-root user
RUN addgroup -g ${PGID} ${USER} && \
adduser -D -u ${PUID} -G ${USER} -s /bin/sh ${USER}
WORKDIR ${WORKDIR}
# Development stage
FROM base AS development
# Install development tools
RUN apk add --no-cache \
bash \
curl \
git \
vim
# Install development dependencies
COPY requirements.txt requirements-dev.txt ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system --no-cache -r requirements-dev.txt
# Copy source code
COPY --chown=${USER}:${USER} . .
USER ${USER}
CMD ["python", "run.py"]
# Production stage
FROM base AS production
# Install runtime system dependencies
RUN apk add --no-cache \
bash \
curl
# Install production dependencies only
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system --no-cache -r requirements.txt
# Copy application code
COPY --chown=${USER}:${USER} app/ ./app/
COPY --chown=${USER}:${USER} run.py .
# Switch to non-root user
USER ${USER}
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Expose port
EXPOSE 8000
# Run application
CMD ["python", "run.py"]
Health Checks
# HTTP service
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Database
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD pg_isready -U postgres || exit 1
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Docker Compose File Organization
project/
├── docker-compose.yml
├── compose/
│ ├── dev.yml
│ ├── service1.yml
│ └── service2.yml
├── .env
├── .env.development
└── .env.production
services:
app:
build:
context: .
dockerfile: Dockerfile
target: production
environment:
- DATABASE_URL=${DATABASE_URL}
- LOG_LEVEL=${LOG_LEVEL:-info}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
depends_on:
db:
condition: service_healthy
networks:
- app_network
db:
image: postgres:15-alpine
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app_network
volumes:
db_data:
networks:
app_network:
driver: bridge
Development Workflow
| Aspect | Development | Production |
|---|---|---|
| Base Image | Full OS for debugging | Alpine (minimal) |
| Code Mount | Volume mount for hot reload | Copied into image |
| Dependencies | Include dev tools | Runtime only |
| Ports | Exposed for debugging | Necessary only |
| Restart | no (manual control) | unless-stopped |
| Logging | DEBUG | INFO/WARN |
# compose/dev.yml
services:
app:
build:
target: development
volumes:
- .:/workspace:cached
command: python run.py --reload
environment:
- ENVIRONMENT=development
- LOG_LEVEL=debug
DevContainer Configuration
{
"name": "Project Dev Container",
"dockerComposeFile": "../docker-compose.yml",
"service": "dev",
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-azuretools.vscode-docker"
]
}
},
"postCreateCommand": "pip install -e .[dev]",
"remoteUser": "appuser"
}
Makefile Integration Pattern
.PHONY: dev build up down logs
dev:
@docker compose -f docker-compose.yml -f compose/dev.yml up
build:
@docker compose build
up:
@docker compose up -d
down:
@docker compose down
logs:
@docker compose logs -f
# Pattern rules
run-%:
@docker compose -f docker-compose.yml -f compose/$*.yml up
stop-%:
@docker compose stop $*
Environment Variables
Build-time Variables (ARG)
- Use ARG for build-time configuration
- Common ARGs: PUID, PGID, USER, WORKDIR, VERSION
ARG PYTHON_VERSION=3.12
ARG PUID=1000
ARG PGID=1000
ARG USER=appuser
ARG WORKDIR=/app
Runtime Variables (ENV)
- Use ENV for runtime environment variables
- Provide sensible defaults
- Document required vs. optional variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
APP_ENV=production
.dockerignore Best Practices
Comprehensive .dockerignore organized by category to exclude unnecessary files from build context:
# Git
.git/
.gitignore
.gitattributes
# Documentation
README.md
docs/
*.md
# CI/CD
.github/
.gitlab-ci.yml
# Development
.vscode/
.idea/
.devcontainer/
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
.coverage
htmlcov/
.venv/
venv/
*.egg-info/
# Environment files
.env
.env.*
# Build artifacts
build/
dist/
# Testing
tests/
.spec/
# Logs
*.log
# System
.DS_Store
.windows
Thumbs.db
# Docker files
docker-compose*.yml
Dockerfile*
.dockerignore
# Node (if applicable)
node_modules/
# Misc
*.tmp
.cache/
Network Configuration
networks:
app_network:
driver: bridge
db_network:
driver: bridge
internal: true # No external access
Language-Specific Patterns
Flask Application
FROM python:3.12-alpine AS production
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ARG USER=appuser
RUN adduser -D -s /bin/sh ${USER}
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system --no-cache -r requirements.txt
COPY --chown=${USER}:${USER} app/ ./app/
COPY --chown=${USER}:${USER} run.py .
USER ${USER}
EXPOSE 5000
HEALTHCHECK CMD curl -f http://localhost:5000/health || exit 1
CMD ["python", "run.py"]
Background Worker
FROM python:3.12-alpine AS production
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ARG USER=worker
RUN adduser -D -s /bin/sh ${USER}
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system --no-cache -r requirements.txt
COPY --chown=${USER}:${USER} worker/ ./worker/
USER ${USER}
# No EXPOSE needed for background workers
# No HEALTHCHECK - use orchestrator health checks
CMD ["python", "-m", "worker.main"]
BuildKit Features
Cache Mounts
# Cache pip packages
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# Cache npm packages
RUN --mount=type=cache,target=/root/.npm \
npm install
Multi-platform Builds
# Support ARM and AMD architectures
FROM --platform=$BUILDPLATFORM python:3.12-alpine
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Building for $TARGETPLATFORM on $BUILDPLATFORM"
Multi-platform Build Guidance
- Use
docker buildxfor building multiple architectures - Reference build platform variables:
$BUILDPLATFORM,$TARGETPLATFORM - Leverage BuildKit cache exports for CI/CD:
--cache-to=type=registryand--cache-from=type=registry - Pin base image versions to exact patches for consistent caching
Signal Handling and Entrypoint Scripts
Production Containers
- Use
gosuwithexecin production entrypoint scripts to drop privileges and forward signals - Ensure CMD uses direct command execution (not shell wrapping) for proper signal delivery
- When docker.sock is mounted, fix permissions in entrypoint with:
chown ${USER}:${USER} /var/run/docker.sock >/dev/null 2>&1 || true
Development Containers
- Sudo is acceptable for devcontainer usage
Production Entrypoint Script with Signal Handling
#!/bin/bash
set -o errexit # abort on nonzero exitstatus
set -o nounset # abort on unbound variable
set -o pipefail # do not hide errors within pipes
if [ -v DOCKER_ENTRYPOINT_DEBUG ] && [ "$DOCKER_ENTRYPOINT_DEBUG" == 1 ]; then
set -x
set -o xtrace
fi
# If running as root, adjust the ${USER} user's UID/GID and drop to that user
if [ "$(id -u)" = "0" ]; then
groupmod -o -g ${PGID:-1000} ${USER} 2>&1 >/dev/null|| true
usermod -o -u ${PUID:-1000} ${USER} 2>&1 >/dev/null|| true
# Ensure docker.sock is owned by the target user when running as root
chown ${USER}:${USER} /var/run/docker.sock >/dev/null 2>&1 || true
echo "Running as user ${USER}: $@"
exec gosu ${USER} "$@"
fi
echo "Running: $@"
exec "$@"
Common Container Patterns
services:
app:
depends_on:
db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
db:
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data:
Git Submodules in Containers
RUN git clone --recurse-submodules https://github.com/user/repo.git
# Or: RUN git submodule update --init --recursive
Essential Docker Commands
# Service management
docker compose up / up -d / down / down -v
docker compose restart app
docker compose build / build --no-cache
# Monitoring
docker compose ps / logs -f / logs -f app
docker stats
# Execute commands
docker compose exec app sh
docker compose run --rm app pytest
# Cleanup
docker image prune -a
docker system prune
Quick Reference
Before running containers:
- Check README and Makefile
- Review docker-compose.yml dependencies
- Check for .env.example
- Understand dev vs production configs
Common mistakes:
- Using
version:field ordocker-composewith hyphen - Running as root user
- Using large base images (not Alpine)
- Committing secrets
- Using
.localdomain - Skipping health checks
Note: Container projects vary in complexity. Always check project-specific documentation before making changes to Docker configurations.