Docker Helper
Dockerfile Best Practices
Layer Caching and.dockerignore
Order instructions from least to most frequently changing. Copy dependency manifests before source code.
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]Always include a .dockerignore to reduce build context and prevent secret leaks:
node_modules
.git
.env
*.log
dist
__pycache__
.venvHealth Checks
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1Image Management
docker build -t myapp:latest .
docker build -t myapp:v1.2 -f Dockerfile.prod --build-arg NODE_ENV=production .
docker build --no-cache -t myapp:latest .
docker tag myapp:latest registry.example.com/myapp:v1.2
docker push registry.example.com/myapp:v1.2
docker pull registry.example.com/myapp:v1.2
docker images
docker images --filter "dangling=true"
docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
docker history myapp:latest
docker rmi myapp:latest
docker image prune -aContainer Lifecycle
# Run (create + start)
docker run -d --name myapp -p 8080:80 nginx
docker run -it --rm ubuntu bash
docker run -d --restart unless-stopped myapp
# Start / stop / restart
docker start myapp
docker stop myapp
docker restart myapp
# Remove
docker rm myapp
docker rm -f myapp
# List
docker ps # Running
docker ps -a # All
# Logs
docker logs myapp
docker logs -f --tail 200 myapp
docker logs --since 10m myapp
# Execute inside a running container
docker exec -it myapp bash
docker exec -it myapp sh # Alpine
docker exec myapp cat /etc/os-releaseNetworking
docker network ls
docker network create mynet
# Containers on the same custom network resolve each other by name
docker run -d --name api --network mynet myapi:latest
docker run -d --name db --network mynet postgres:16
# From api, reach db at hostname "db": postgresql://user:pass@db:5432/mydb
docker network connect mynet myapp
docker network disconnect mynet myapp
docker network inspect mynet
# Port mapping
docker run -d -p 8080:80 nginx # Host 8080 -> container 80
docker run -d -p 127.0.0.1:8080:80 nginx # Bind to localhost only
docker run -d -P nginx # All exposed -> random host ports
# Host networking (Linux only, container shares host network stack)
docker run -d --network host nginxVolumes and Bind Mounts
# Named volume
docker volume create mydata
docker run -d -v mydata:/var/lib/data myapp
# Bind mount
docker run -d -v $(pwd)/src:/app/src myapp
docker run -d -v $(pwd)/config.yaml:/app/config.yaml:ro myapp
# tmpfs (in-memory, never written to disk)
docker run -d --tmpfs /tmp:rw,size=64m myapp
docker volume ls
docker volume inspect mydata
docker volume rm mydata
# Copy files
docker cp myapp:/app/data.json ./
docker cp ./config.yaml myapp:/app/Environment Variables and Secrets
docker run -e DATABASE_URL=postgres://user:pass@db:5432/app myapp
docker run --env-file .env myappBuildKit secrets (never stored in image layers):
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ciMulti-Stage Build Patterns
Go (scratch)
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app .
FROM scratch
COPY --from=builder /app /app
ENTRYPOINT ["/app"]Python
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN python -m venv /opt/venv && /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY . .
USER nobody
CMD ["python", "main.py"]Node.js
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
RUN addgroup -g 1001 appgroup && adduser -S -u 1001 -G appgroup appuser
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]Java
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY build.gradle settings.gradle gradlew ./
COPY gradle ./gradle
RUN ./gradlew dependencies --no-daemon
COPY src ./src
RUN ./gradlew bootJar --no-daemon
FROM eclipse-temurin:21-jre
COPY --from=builder /app/build/libs/*.jar app.jar
USER 1000
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]Debugging Containers
docker inspect myapp
docker inspect --format '{{.State.Status}}' myapp
docker inspect --format '{{.State.ExitCode}}' myapp
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myapp
docker stats # Live resource usage
docker stats myapp --no-stream # Single snapshot
docker top myapp # Processes inside container
docker diff myapp # Filesystem changes since start
docker events --filter container=myapp
# Debug sidecar with network tools
docker run -it --rm --pid=container:myapp --network=container:myapp nicolaka/netshootDocker Buildx (Multi-Platform)
docker buildx create --name multibuilder --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
docker buildx build --platform linux/amd64 -t myapp:latest --load .
docker buildx ls
docker buildx rm multibuilderImage Optimization
| Base Image | Size | Shell | Use Case |
|---|---|---|---|
| ubuntu:22.04 | ~77 MB | Yes | General purpose |
| debian:bookworm-slim | ~52 MB | Yes | Smaller general purpose |
| alpine:3.19 | ~7 MB | Yes | Small images with shell |
| gcr.io/distroless/base | ~20 MB | No | Minimal attack surface |
| scratch | 0 MB | No | Static binaries (Go, Rust) |
- Combine RUN statements:
RUN apt-get update && apt-get install -y pkg && rm -rf /var/lib/apt/lists/* - Remove caches in the same layer they are created.
- Use
docker historyanddiveto find large layers.
Security
# Non-root user
RUN addgroup -g 1001 appgroup && adduser -S -u 1001 -G appgroup appuser
USER appuser# Read-only filesystem
docker run --read-only --tmpfs /tmp myapp
# Drop capabilities
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
# Prevent privilege escalation
docker run --security-opt no-new-privileges myapp
# Limit resources
docker run --memory 512m --cpus 1.0 myappNever use COPY or ENV for secrets. Use BuildKit secret mounts or runtime env vars.
Scanning with Trivy
trivy image myapp:latest
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest
trivy config DockerfileCleanup Commands
docker container prune # Remove stopped containers
docker image prune -a # Remove all unused images
docker volume prune # Remove unused volumes (data loss risk)
docker network prune # Remove unused networks
docker system prune -a --volumes # Remove everything unused
docker system df # Check disk usageTroubleshooting
Container Exits Immediately
docker logs myapp
docker inspect --format '{{.State.ExitCode}}' myapp
# Exit 0: process completed (not a daemon, or CMD missing)
# Exit 1: application error
# Exit 137: OOM killed or SIGKILL
# Exit 126: command not executable
# Exit 127: command not found (check CMD/ENTRYPOINT path)Port Conflicts
lsof -i :8080 # Find what uses the port
docker run -d -p 9090:80 nginx # Use a different host portPermission Issues
COPY --chown=appuser:appgroup . /appdocker run -u $(id -u):$(id -g) -v $(pwd):/app myappContainer Cannot Resolve Other Containers
Default bridge does not support DNS. Use a user-defined network:
docker network create mynet
docker run -d --name api --network mynet myapi
docker run -d --name db --network mynet postgres:16Build Context Too Large
Add paths to .dockerignore, or specify a smaller context directory:
docker build -t myapp -f Dockerfile ./src