Caddy Setup
Overview
Caddy is a modern web server that handles HTTPS automatically, reverse proxies, and static file serving with minimal configuration. Unlike traditional servers, Caddy obtains and renews SSL certificates from Let’s Encrypt on its own — no separate tool like Certbot required.
This guide runs Caddy as a Docker container via Docker Compose, which keeps the host clean and makes the configuration portable.
Prerequisites
- VPS setup completed (see VPS Setup)
- UFW configured (see UFW Setup)
- Docker installed and configured (see Docker Setup)
- Docker Compose installed (see Docker Compose)
- Domain name pointed to your VPS IP
What is a domain? A domain (like
example.com) is a human-readable address that points to your server’s IP address. You can buy domains from registrars like Namecheap, Cloudflare, or Google Domains. After buying a domain, you need to create anArecord in your DNS settings that points to your VPS’s IP address. Caddy needs this to verify you own the domain and issue an SSL certificate.
Firewall Rules
Caddy needs ports 80 (HTTP) and 443 (HTTPS) open so it can serve traffic and complete ACME challenges:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Docker Compose Deployment
Create the Caddy directory:
sudo mkdir -p /opt/caddy && cd /opt/caddy
Create the Caddyfile:
sudo vim Caddyfile
A minimal reverse proxy configuration looks like this:
<domain> {
reverse_proxy <service-name>:<port>
}
Create docker-compose.yml:
services:
caddy:
image: caddy:2
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- caddy
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
networks:
caddy:
external: true
Create the external network before starting Caddy:
sudo docker network create caddy
| Volume / Network | Purpose |
|---|---|
./Caddyfile | Your server configuration |
caddy_data | TLS certificates and state (persisted) |
caddy_config | Caddy’s internal config |
caddy (external) | Shared network for Caddy to reach other containers |
Start Caddy:
sudo docker compose up -d
Caddy will automatically obtain an SSL certificate for <domain> and begin serving HTTPS.
Connecting Other Services
Each of your services lives in its own Docker Compose project under /opt. To let Caddy reverse proxy to them, attach each service to the external caddy network.
In the service’s docker-compose.yml, add:
services:
<service-name>:
# ... existing config ...
networks:
- caddy
networks:
caddy:
external: true
Then recreate the container to join the network:
cd /opt/<service-name>
sudo docker compose up -d
Once connected, Caddy can reach the service by its Compose service name:
<domain> {
reverse_proxy <service-name>:<port>
}
Caddyfile Basics
Reverse Proxy to a Container
With the shared caddy network, use the service name from the target Compose file:
calibre.<yourdomain>.com {
reverse_proxy calibre-web:8083
}
Static File Serving
Serve files from a directory inside the Caddy container:
<domain> {
root * /usr/share/caddy
file_server
}
Mount the files into the container:
services:
caddy:
# ...
volumes:
- ./site:/usr/share/caddy
Multiple Sites
Caddy handles multiple sites in one Caddyfile:
calibre.<yourdomain>.com {
reverse_proxy calibre-web:8083
}
linkding.<yourdomain>.com {
reverse_proxy linkding:9090
}
Common Commands
cd /opt/caddy
sudo docker compose up -d # Start Caddy
sudo docker compose down # Stop and remove
sudo docker compose logs -f caddy # Follow logs
sudo docker compose restart caddy # Restart after Caddyfile changes
After editing Caddyfile, Caddy auto-reloads in most cases. If not, restart the container:
sudo docker compose restart caddy
Notes
- Caddy stores certificates in the
caddy_datavolume. Do not delete this volume unless you want to reissue certificates. - The
caddyDocker network is external so multiple Compose projects can attach to it. Create it once withdocker network create caddy. - If you need to use a wildcard certificate or a DNS provider challenge, Caddy supports those via modules, but the default HTTP/ALPN challenge works for standard domains without extra configuration.