█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/DevOps and Infrastructure/Docker and Containerization/Running Real Applications in Docker
60 minIntermediate

Running Real Applications in Docker

After this lesson, you will be able to: Containerize a real app: Next.js + Python API + PostgreSQL behind a Caddy reverse proxy, wired together with Docker Compose.

Time to put it all together. By the end of this lesson, you have a multi-container stack you can `docker compose up` in one command.

Prerequisites:Optimizing Docker Images

Dockerfile: Next.js app

Multi-stage, slim runtime, non-root, healthcheck-ready.

tsx
# apps/web/Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/public ./public
COPY --from=builder --chown=app:app /app/.next/standalone ./
COPY --from=builder --chown=app:app /app/.next/static ./.next/static
USER app
EXPOSE 3000
CMD ["node", "server.js"]
# Add output: 'standalone' to next.config.js for the .next/standalone path to exist.

Dockerfile: Python FastAPI API

Equivalent for Python.

tsx
# apps/api/Dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
RUN useradd -m -u 1000 app
COPY --from=builder --chown=app:app /root/.local /home/app/.local
COPY --chown=app:app . .
ENV PATH=/home/app/.local/bin:$PATH PYTHONUNBUFFERED=1
USER app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml: full stack with Caddy reverse proxy

Caddy gives free auto-HTTPS in dev (via /etc/hosts + local CA) and prod (Let's Encrypt).

tsx
services:
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- web
- api
restart: unless-stopped
web:
build: ./apps/web
environment:
NEXT_PUBLIC_API_URL: https://api.example.com
depends_on:
api:
condition: service_healthy
restart: unless-stopped
api:
build: ./apps/api
environment:
DATABASE_URL: postgresql://app:app@db:5432/app
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5
restart: unless-stopped
volumes:
pgdata:
caddy_data:
caddy_config:

Caddyfile (Caddy is the simplest reverse proxy)

Put this next to docker-compose.yml.

tsx
example.com {
reverse_proxy web:3000
}
api.example.com {
reverse_proxy api:8000
}
# Local dev: replace example.com with localhost; Caddy uses a local CA.
# Production: Caddy automatically requests Let's Encrypt certs.

Common mistakes only experienced engineers avoid

Forgetting `restart: unless-stopped`. After a host reboot, the stack doesn't come back. Healthchecks that never go healthy (the command isn't installed in the slim image). Test from `docker exec`. Reverse-proxying TO the public port. Caddy should talk to web on its internal port 3000, not via a `-p` published port. Mounting source code into production containers (bind mount). Production containers should be immutable. Skipping `read_only: true` for stateless services. The base image filesystem doesn't need to be writable.

Quick Check

Why use a reverse proxy (Caddy / nginx) in front of your app containers?

Pick the most senior reason.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Optimizing Docker Images
Back to Docker and Containerization
Container Security→