This episode covers Docker deployment: the official Caddy image, multi- stage builds, Docker Compose with volumes and networks, certificate persistence, service discovery between containers, and Caddy as a reverse proxy for many applications.

Containers changed how applications are deployed, and Caddy is one of the most comfortable web servers for Docker. Its official image is lightweight, configuration comes from an easily mountable file, and logs go straight to stdout for container management. Episode 27 covers Docker integration comprehensively.
The official caddy image is available on Docker Hub:
docker run -d -p 80:80 -p 443:443 \
--name caddy \
-v $PWD/Caddyfile:/etc/caddy/Caddyfile:ro \
-v caddy_data:/data \
-v caddy_config:/config \
caddy:2Important volumes:
Caddyfile:/etc/caddy/Caddyfile — the configuration.caddy_data:/data — certificates and storage (must be persistent).caddy_config:/config — runtime configuration.docker run -d -p 80:80 -p 443:443 caddy:2 maps the standard web ports to the container.
caddy:2 — the standard image.caddy:2-alpine — a smaller Alpine-based version.xcaddy (episode 29).FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM caddy:2-alpine
COPY --from=build /app/dist /srv
COPY Caddyfile /etc/caddy/Caddyfile:80 {
root * /srv
try_files {path} /index.html
file_server
}services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- web
app:
build: .
networks:
- web
db:
image: postgres:16
networks:
- web
volumes:
caddy_data:
caddy_config:
networks:
web:On a shared network, the service name becomes the hostname:
app.example.com {
reverse_proxy app:3000
}The caddy_data volume stores ACME certificates. If the volume is lost, Caddy requests certificates again — which can hit CA rate limits. Make sure:
docker exec caddy caddy reload --config /etc/caddy/Caddyfiledocker exec caddy caddy reload runs the command inside the container — a seamless reload. Or simply restart the container; Caddy reads the Caddyfile on start.
The official Caddy image sends logs to stdout/stderr. Docker captures them automatically:
docker logs -f caddyapi.example.com {
reverse_proxy api:3000
}
web.example.com {
reverse_proxy web:3000
}
blog.example.com {
root * /srv/blog
file_server
}caddy-docker-proxy plugin (episode 29) to manage via labels.Episode 27 covered Docker: the official image with persistent volumes, multi-stage builds for static applications, Docker Compose with a shared network, certificate persistence in caddy_data, service discovery via container names, and the one-Caddy-many-applications pattern.
Key takeaways:
caddy_data volume must be persistent for certificates.docker exec caddy caddy reload reloads without restart.docker logs.In the next episode, episode 28, we'll cover Kubernetes deployment — Deployments, Services, ConfigMaps, and PersistentVolumeClaims, Caddy as an ingress, Caddyfile management in a ConfigMap, cert-manager integration, service discovery via K8s DNS, and Helm charts.