12 Prompts for Docker: From Dockerfile to Multi-Stage Builds

Why You Need These Docker Prompts

Docker has become the de facto standard for containerization, used by 90% of organizations according to the 2025 Cloud Native Computing Foundation annual survey. Yet many developers still struggle with writing efficient Dockerfiles, orchestrating multi-container applications with Compose, or optimizing image sizes. A poorly written Dockerfile can balloon an image to several gigabytes, increasing deployment times and storage costs.

This collection of 12 prompts covers the full spectrum of Docker usage—from beginner-friendly Dockerfile patterns to advanced multi-stage and security-hardening techniques. Each prompt includes a clear objective, the exact prompt text, a practical example, and the expected output. Whether you are a DevOps engineer, a backend developer, or a data scientist packaging ML models, these prompts will help you write better Docker configurations faster.

The prompts are organized into three categories: Basic (Dockerfile fundamentals), Advanced (Compose and networking), and Expert (optimization, security, and debugging). Use them as templates you can adapt to your own projects.

Basic Prompts: Dockerfile Fundamentals

1. Generate a Minimal Dockerfile for a Node.js App

Task: Create a production-ready Dockerfile for a Node.js Express application.

Prompt:

Write a Dockerfile for a Node.js Express app that:
- Uses the official Node.js 20 Alpine image as base
- Sets the working directory to /app
- Copies package.json and package-lock.json first
- Runs npm ci --only=production
- Copies the rest of the source code
- Exposes port 3000
- Runs the app with node index.js
- Uses a non-root user (node)

Example Result:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
    chown -R node:node /app
COPY --chown=node:node . .
USER node
EXPOSE 3000
CMD ["node", "index.js"]

This Dockerfile follows best practices: it uses a slim base image, leverages layer caching by copying package files first, installs only production dependencies, and runs as a non-root user for security. The node user exists in the official Node.js image by default.

2. Generate a Dockerfile for a Python Flask API

Task: Build a Docker image for a Flask application with Gunicorn as the WSGI server.

Prompt:

Create a two-stage Dockerfile for a Flask app that:
- Stage 1: Install dependencies from requirements.txt using Python 3.11-slim
- Stage 2: Copy only the installed packages and the application code to a clean slim image
- Uses Gunicorn with 4 workers
- Exposes port 5000
- Sets environment variables for Flask env and secret key
- Runs the app with exec form

Example Result:

# Stage 1: Build dependencies
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Stage 2: Runtime image
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
ENV FLASK_ENV=production
ENV SECRET_KEY=change-me-in-production
EXPOSE 5000
CMD ["gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "app:app"]

Multi-stage builds reduce the final image size significantly—the first stage may be 800 MB, but the final image is often under 200 MB. This is a common pattern for Python applications.

3. Generate a Dockerfile for a Static Site (Nginx)

Task: Serve a static HTML/CSS/JS site using Nginx in a Docker container.

Prompt:

Write a Dockerfile for a static website that:
- Uses the official Nginx Alpine image
- Copies the static files from a local 'dist' directory to /usr/share/nginx/html
- Customizes the Nginx configuration to enable gzip
- Exposes port 80
- Runs Nginx in the foreground

Example Result:

FROM nginx:alpine
COPY ./dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

With the custom nginx.conf, you can enable gzip compression (reducing transfer sizes by 60-70%), set cache headers, and configure routing for single-page applications. The Alpine-based image is only about 23 MB.

Advanced Prompts: Docker Compose and Networking

4. Generate a Docker Compose File for a Web App with PostgreSQL

Task: Orchestrate a Node.js web application with a PostgreSQL database using Docker Compose.

Prompt:

Create a docker-compose.yml for a Node.js app that:
- Defines two services: 'web' and 'db'
- The web service builds from the current directory and maps port 3000:3000
- The db service uses PostgreSQL 16 Alpine image
- Sets environment variables for DB name, user, and password
- Uses a named volume for persistent database storage
- Creates a custom network for inter-service communication
- Adds a healthcheck for the db service

Example Result:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - DB_USER=app_user
      - DB_PASSWORD=secret123
      - DB_NAME=myapp
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=app_user
      - POSTGRES_PASSWORD=secret123
      - POSTGRES_DB=myapp
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

volumes:
  pgdata:

networks:
  app-network:
    driver: bridge

This Compose file ensures that the web service waits for the database to be healthy before starting, preventing race conditions. Named volumes guarantee data persistence across container restarts.

5. Generate a Docker Compose File with Redis and Background Worker

Task: Set up a web application with a Redis message broker and a Celery worker.

Prompt:

Write a docker-compose.yml for a Python app that uses:
- A Flask web service
- A Redis service (official Redis 7 Alpine image)
- A Celery worker service that runs the same image as the web service but with a different command
- All services on the same network
- Redis data stored in a named volume

Example Result:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "5000:5000"
    command: gunicorn -w 4 -b 0.0.0.0:5000 app:app
    environment:
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - redis
    networks:
      - backend

  worker:
    build: .
    command: celery -A tasks worker --loglevel=info
    environment:
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - redis
    networks:
      - backend

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    networks:
      - backend

volumes:
  redis-data:

networks:
  backend:

This pattern is common for applications that need asynchronous task processing. The worker service uses the same Dockerfile as the web service but overrides the startup command, keeping the codebase unified.

6. Generate a Docker Compose File with Traefik Reverse Proxy

Task: Use Traefik as a reverse proxy to route traffic to multiple services.

Prompt:

Create a docker-compose.yml that:
- Defines a Traefik service with ports 80 and 8080 (dashboard)
- Has two backend services: 'api' and 'frontend'
- Each backend service has Traefik labels for host-based routing
- Traefik uses the Docker provider
- Enable the Traefik dashboard with basic auth

Example Result:

version: '3.8'
services:
  traefik:
    image: traefik:v2.10
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--api.dashboard=true"
      - "--api.insecure=false"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$10$$..."

  api:
    image: my-api:latest
    labels:
      - "traefik.http.routers.api.rule=Host(`api.localhost`)"
      - "traefik.http.services.api.loadbalancer.server.port=3000"

  frontend:
    image: my-frontend:latest
    labels:
      - "traefik.http.routers.frontend.rule=Host(`app.localhost`)"
      - "traefik.http.services.frontend.loadbalancer.server.port=80"

Traefik automatically discovers services via Docker labels, eliminating the need for manual Nginx configuration. The dashboard provides real-time metrics and routing visualization.

Expert Prompts: Optimization, Security, and Debugging

7. Generate a Multi-Stage Dockerfile for a Go Application

Task: Build a minimal Go binary using multi-stage builds.

Prompt:

Write a multi-stage Dockerfile for a Go application that:
- Stage 1: Uses golang:1.22-alpine to compile the binary with CGO_ENABLED=0
- Stage 2: Uses scratch (empty base image) to run the binary
- Copies the compiled binary from stage 1
- Copies the SSL certificates from alpine for HTTPS calls
- Exposes port 8080
- Runs the binary as a non-root user (use USER 1001)

Example Result:

# Stage 1: Build
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache ca-certificates
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/myapp ./cmd/myapp

# Stage 2: Runtime
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/myapp /myapp
USER 1001
EXPOSE 8080
ENTRYPOINT ["/myapp"]

The final image can be as small as 10-15 MB for a Go application. Using scratch minimizes the attack surface because it contains no shell, package manager, or utilities.

8. Generate a Dockerfile with Layer Optimization and Caching

Task: Optimize a Dockerfile to minimize rebuild time by maximizing layer cache hits.

Prompt:

Refactor this Dockerfile to improve layer caching:
- Always copy package.json and package-lock.json before other source files
- Combine RUN commands that are related to installing dependencies
- Use --no-cache-dir for pip installations
- Sort COPY instructions by frequency of change (least changing first)
- Add a .dockerignore file to exclude node_modules and .git

Example Result:

FROM node:20-alpine
WORKDIR /app

# Copy dependency files first (rarely change)
COPY package.json package-lock.json ./

# Install dependencies (cached until package files change)
RUN npm ci --only=production && npm cache clean --force

# Copy application code (changes frequently)
COPY . .

EXPOSE 3000
CMD ["node", "index.js"]

And the accompanying .dockerignore:

node_modules
.git
.env
npm-debug.log*

Proper layer ordering can reduce build times by 70-80% in CI/CD pipelines. According to Docker's official documentation, each instruction in a Dockerfile creates a layer that is cached unless the context changes.

9. Generate a Docker Compose File with Resource Limits and Health Checks

Task: Add CPU and memory limits to services, plus comprehensive health checks.

Prompt:

Create a docker-compose.yml that:
- Sets memory limit of 512 MB and CPU limit of 0.5 for the web service
- Sets memory limit of 256 MB for the database service
- Adds a health check to the web service that curls the /health endpoint
- Uses restart: unless-stopped for all services
- Configures logging with max-size and max-file

Example Result:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  db:
    image: postgres:16-alpine
    deploy:
      resources:
        limits:
          memory: 256M
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Resource limits prevent a single container from exhausting host resources. Health checks enable Docker to automatically restart unhealthy containers.

10. Generate a Dockerfile with Security Scanning and Non-Root User

Task: Harden a Docker image against common vulnerabilities.

Prompt:

Write a Dockerfile that:
- Uses a specific image digest instead of a tag for immutability
- Creates a dedicated non-root user
- Removes setuid/setgid binaries
- Runs the application with read-only root filesystem (if possible)
- Uses --no-install-recommends for apt-get
- Copies only necessary files

Example Result:

FROM node:20-alpine@sha256:abc123...

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Remove setuid/setgid binaries from base image
RUN find / -perm /6000 -type f -exec chmod a-s {} ; || true

COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production
COPY --chown=appuser:appgroup . .

USER appuser
EXPOSE 3000
CMD ["node", "index.js"]

According to the CIS Docker Benchmark, running containers as non-root and using read-only filesystems are two of the most effective measures to mitigate container escape vulnerabilities.

11. Generate a Dockerfile for a Machine Learning Model Serving

Task: Package a TensorFlow model with a FastAPI serving endpoint.

Prompt:

Create a Dockerfile for a machine learning inference service that:
- Uses tensorflow/serving:latest as the base for the model server
- Copies a pre-trained SavedModel to the expected model directory
- Sets the MODEL_NAME environment variable
- Exposes port 8501 (REST API) and 8500 (gRPC)
- Configures batching parameters for performance
- Runs with minimal permissions

Example Result:

FROM tensorflow/serving:latest

# Copy the model
COPY ./models /models

# Set environment variables
ENV MODEL_NAME=my_model
ENV TF_CPP_MIN_LOG_LEVEL=3

# Configure batching
ENV ENABLE_BATCHING=true
ENV MAX_BATCH_SIZE=32
ENV BATCH_TIMEOUT_MICROS=1000

# Expose ports
EXPOSE 8500 8501

# Run as non-root
USER 1000

TensorFlow Serving can handle thousands of inference requests per second when batching is properly configured. The REST API at port 8501 returns predictions in JSON format.

12. Generate a Docker Debugging Container with Tools

Task: Create a utility container for debugging network issues in a Docker environment.

Prompt:

Write a Dockerfile for a debugging container that includes:
- Alpine Linux base
- Networking tools: curl, wget, netcat, iproute2, bind-tools (dig)
- Process tools: htop, procps, lsof
- Editor: vim or nano
- Sets the container to sleep indefinitely so you can exec into it
- Labels it with 'type=debug'

Example Result:

FROM alpine:latest

RUN apk add --no-cache \
    curl \
    wget \
    netcat-openbsd \
    iproute2 \
    bind-tools \
    htop \
    procps \
    lsof \
    vim \
    bash

LABEL type=debug

CMD ["sleep", "infinity"]

You can build and run it with:

docker build -t debug-container .
docker run -d --name debugger --network host debug-container
docker exec -it debugger bash

This container is invaluable for troubleshooting DNS resolution, connectivity between services, and inspecting container resource usage.

Conclusion

These 12 prompts cover the essential spectrum of Docker usage—from writing your first Dockerfile to optimizing images for production and debugging complex network issues. Each prompt is designed to be a reusable template that you can adapt to your specific technology stack.

The key takeaways are:
- Always use multi-stage builds to minimize image size
- Leverage layer caching by ordering COPY and RUN instructions wisely
- Set resource limits and health checks in Docker Compose for production reliability
- Run containers as non-root users to reduce security risks
- Use debugging containers to troubleshoot network and configuration issues

Start by implementing the basic prompts for your current projects, then gradually adopt the advanced and expert patterns. The Docker documentation at docs.docker.com remains the authoritative source for best practices. Share your own Docker prompts in the comments below—the community benefits when we all contribute our battle-tested patterns.

← All posts

Comments