Caddy Docker Setup Guide
This guide explains how to set up Caddy as a reverse proxy with Cloudflare DNS integration for automatic SSL certificates.
Prerequisites
- Docker and Docker Compose installed on your remote server
- Cloudflare account with your domain configured
- Cloudflare API token with
Zone:DNS:Editpermissions
Directory Structure
Create the following directory structure on your remote server:
mkdir -p /opt/caddy/{config,data,logs}
cd /opt/caddy1. Create Dockerfile
Create /opt/caddy/Dockerfile:
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare
FROM caddy:2-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy2. Create docker-compose.yml
Create /opt/caddy/docker-compose.yml:
version: '3.8'
services:
caddy:
build: .
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "2019:2019" # Admin API - SECURE THIS!
environment:
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
- ACME_EMAIL=${ACME_EMAIL}
- CADDY_ADMIN_KEY=${CADDY_ADMIN_KEY}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data
- ./config:/config
- ./logs:/logs
networks:
- caddy
networks:
caddy:
external: true3. Create Initial Caddyfile
Create /opt/caddy/Caddyfile:
{
# Global options
admin :2019 {
origins localhost
api_key {$CADDY_ADMIN_KEY}
}
# ACME DNS challenge via Cloudflare
acme_dns cloudflare {$CLOUDFLARE_API_TOKEN}
email {$ACME_EMAIL}
# Logging
log {
output file /logs/caddy.log
level INFO
}
}
# Default site (catches all unmatched requests)
:443 {
respond "No site configured for this domain" 404
}
# Health check endpoint
:2019/health {
respond "OK" 200
}4. Create Environment File
Create /opt/caddy/.env:
# Cloudflare API Token (with Zone:DNS:Edit permissions)
CLOUDFLARE_API_TOKEN=your_cloudflare_api_token_here
# Email for Let's Encrypt notifications
ACME_EMAIL=ssl@yourdomain.com
# API Key for Caddy Admin API
CADDY_ADMIN_KEY=generate_a_secure_api_key_hereImportant: Generate a secure API key:
openssl rand -hex 325. Secure the Admin API
Option A: Firewall Rules (Recommended)
# Allow only specific IPs to access the Admin API
sudo ufw allow from YOUR_IP_ADDRESS to any port 2019
# Or use iptables
sudo iptables -A INPUT -p tcp --dport 2019 -s YOUR_IP_ADDRESS -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 2019 -j DROPOption B: VPN/Tailscale Access
Install Tailscale on the server and only expose port 2019 on the Tailscale interface:
# In docker-compose.yml, modify ports:
ports:
- "80:80"
- "443:443"
- "100.64.0.1:2019:2019" # Tailscale IP onlyOption C: SSH Tunnel
Don't expose port 2019 publicly and use SSH tunnel:
# From your local machine
ssh -L 2019:localhost:2019 user@server6. Create Docker Network
docker network create caddy7. Build and Start Caddy
cd /opt/caddy
docker-compose build
docker-compose up -d8. Verify Installation
# Check if Caddy is running
docker-compose ps
# Check logs
docker-compose logs -f
# Test Admin API (from allowed IP)
curl -H "Authorization: Bearer YOUR_API_KEY" https://your-server:2019/config/9. Configure Systemd (Optional)
Create /etc/systemd/system/caddy-docker.service:
[Unit]
Description=Caddy Docker Container
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/caddy
ExecStart=/usr/local/bin/docker-compose up -d
ExecStop=/usr/local/bin/docker-compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetEnable the service:
sudo systemctl enable caddy-docker
sudo systemctl start caddy-docker10. Configure Your App
In your local app, configure the Caddy settings:
- Caddy API URL:
https://your-server:2019(or via tunnel) - API Key: The key you generated in step 4
- Cloudflare API Token: Your Cloudflare token
Security Best Practices
- Never expose port 2019 to the public internet
- Use strong API keys (32+ characters)
- Regularly update Caddy and Docker images
- Monitor logs for suspicious activity
- Use fail2ban to protect against brute force
- Enable Docker's built-in TLS for the daemon
- Consider using Caddy's built-in basic auth for additional security
Troubleshooting
Certificate Issues
Check Caddy logs:
docker-compose logs caddy | grep -i errorAPI Connection Issues
Test from the server:
curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:2019/config/DNS Validation Failed
Ensure your Cloudflare API token has the correct permissions:
- Zone:DNS:Edit
- Zone:Zone:Read
Maintenance
Update Caddy
cd /opt/caddy
docker-compose pull
docker-compose build
docker-compose up -dBackup Configuration
# Backup data and config
tar -czf caddy-backup-$(date +%Y%m%d).tar.gz data/ config/View Certificates
docker exec caddy caddy list-certificatesExample Route Configuration
Once connected to your app, routes will be automatically configured via the Admin API. Here's an example of what gets pushed:
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [":443"],
"routes": [{
"match": [{"host": ["app.example.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "10.0.0.10:3000"}],
"health_checks": {
"active": {
"path": "/health",
"interval": "30s"
}
}
}]
}]
}
}
}
}
}