A production-ready Caddy reverse proxy setup with Docker Compose, featuring automatic HTTPS via Let's Encrypt and Cloudflare DNS. Designed for zero-downtime deployments and dynamic service discovery.
- Features
- Prerequisites
- Quick Start
- Configuration
- Project Structure
- Usage
- Testing
- Troubleshooting
- License
- Zero Config: Uses Docker labels for configuration
- Automatic HTTPS: Via Let's Encrypt with Cloudflare DNS challenge
- Dynamic Service Discovery: Add/remove services without restarting Caddy
- Multi-Architecture: Supports both AMD64 and ARM64
- Built-in Testing: Ansible tests for local and CI environments
- Simple Management: Comprehensive Makefile for common tasks
- Docker and Docker Compose
- Domain with DNS managed by Cloudflare
- Cloudflare API token with DNS edit permissions
mkcertfor local development (optional)
-
Clone the repository:
git clone <repository-url> caddy2 cd caddy2
-
Set up environment:
cp .env.example .env # Edit .env with your configuration -
Start services:
make up
-
Verify installation:
make test
Create or edit .env file with these variables:
# Required for Cloudflare DNS challenge
DOMAIN=yourdomain.com
CF_API_TOKEN=your_cloudflare_token
# Optional: Email for Let's Encrypt notifications
# [email protected]
# Optional: Staging mode for testing (set to 'true' to avoid rate limits)
# STAGING=false
# Optional: Additional domains (space-separated)
# EXTRA_DOMAINS=www.yourdomain.com api.yourdomain.com
# Optional (defaults shown)
API_SUBDOMAIN=api
WEB_SUBDOMAIN=app
AUTH_SUBDOMAIN=authAdd new services to docker-compose.yml with these labels:
service-name:
image: your-image:tag
labels:
- caddy=${SERVICE_SUBDOMAIN}.${DOMAIN}
- caddy.reverse_proxy={{upstreams 80}}
networks:
- app-networkmake test # Auto-detects environmentmake test-dockermake test-local| Command | Description |
|---|---|
make up |
Start all services in detached mode |
make down |
Stop and remove all containers |
make restart |
Restart all services |
make logs |
View logs from all services |
make status |
Show status of all containers |
make test |
Run tests (auto-detects environment) |
make test-docker |
Run tests in Docker container |
make test-local |
Run tests using local Python environment |
make clean |
Remove all containers, networks, and volumes |
- Add your service to
docker-compose.yml - Add appropriate Caddy labels
- Update the
.envfile if needed - Run
make upto deploy
Example service configuration:
service-name:
image: nginx:alpine
labels:
- caddy=service.${DOMAIN}
- caddy.reverse_proxy={{upstreams 80}}
networks:
- app-network- Keep your
.envfile secure and never commit it to version control - Use least-privilege Cloudflare API tokens
- Regularly update your Docker images
- Enable Docker content trust
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a new Pull Request
- Zero external config files (no Caddyfile)
- Automatic HTTPS via Let's Encrypt + Cloudflare DNS
- Dynamic service discovery (add/remove services via Docker labels)
- ARM64 compatible
- Single Docker Compose file
version: '3.8'
networks:
app-network:
driver: bridge
## 📁 Project Structure
caddy2/ ├── .env # Environment variables ├── .env.example # Example environment configuration ├── .gitignore # Git ignore rules ├── Caddyfile # Base Caddy configuration ├── docker-compose.yml # Main Docker Compose configuration ├── Makefile # Project automation ├── README.md # This file ├── config/ # Additional Caddy configuration files │ └── custom-config.json # Custom Caddy JSON config ├── certs/ # SSL certificates (auto-generated) │ ├── localhost.crt # Local development certificate │ └── localhost.key # Local development key └── scripts/ # Utility scripts ├── generate-certs.sh # Certificate generation script └── test/ # Test scripts ├── run_tests.sh # Test runner └── test_api.py # API test cases
## 🚀 Usage
### Starting Services
```bash
# Start all services in detached mode
make up
# View logs
make logs
# Check service status
make status
# Stop services
make down
# Restart services
make restart
# Rebuild and restart
make rebuild# Open shell in Caddy container
make shell
# Reload Caddy configuration
make reload
# Check certificate status
make certsRun the test suite:
# Auto-detect environment and run appropriate tests
make test
# Run tests locally (requires Python and Ansible)
make test-local
# Run tests in Docker container
make test-docker-
Certificate Generation Fails
- Ensure your domain's DNS is properly configured
- Verify Cloudflare API token has correct permissions
- Check Docker logs:
docker-compose logs caddy
-
Port Conflicts
- Stop other services using ports 80/443
- Or modify ports in
docker-compose.yml
-
Container Fails to Start
- Check logs:
make logs - Verify environment variables are set correctly
- Check logs:
# View last 100 log lines
make logs
# Follow logs in real-time
make logs-tailThis project is licensed under the Apache 2.0 License - see the LICENSE file for details.
-
Add monitoring setup
-
Include rate limiting example
-
Add backup/restore scripts
-
Document advanced configurations - CADDY_DOCKER_PROXY_ACME_DNS=cloudflare ${CF_API_TOKEN} - CADDY_DOCKER_PROXY_ACME_EMAIL=[email protected] - DOMAIN=example.com volumes: - /var/run/docker.sock:/var/run/docker.sock ports: - "80:80" - "443:443" networks: - app-network
api: image: your-api-image:latest labels: - caddy=${API_SUBDOMAIN}.${DOMAIN} - caddy.reverse_proxy={{upstreams 8080}} networks: - app-network
web: image: your-web-app:latest labels: - caddy=${WEB_SUBDOMAIN}.${DOMAIN} - caddy.reverse_proxy={{upstreams 3000}} networks: - app-network
auth: image: your-auth-service:latest labels: - caddy=${AUTH_SUBDOMAIN}.${DOMAIN} - caddy.reverse_proxy={{upstreams 4000}} networks: - app-network
---
### 📁 Environment Variables (`.env` file)
```env
DOMAIN=example.com
CF_API_TOKEN=your_cloudflare_api_token
API_SUBDOMAIN=api
WEB_SUBDOMAIN=app
AUTH_SUBDOMAIN=auth
- Dynamic Configuration: The
caddy-docker-proxyimage automatically detects containers withcaddy.*labels. - HTTPS Automation: Uses Cloudflare DNS for Let's Encrypt challenges via the
CADDY_DOCKER_PROXY_ACME_DNSenvironment variable. - Service Discovery: The
{{upstreams PORT}}template routes traffic to the correct container based on exposed ports.
To deploy a new service (e.g., dashboard), simply add it to your Docker Compose with the appropriate labels:
dashboard:
image: your-dashboard:latest
labels:
- caddy=dashboard.example.com
- caddy.reverse_proxy={{upstreams 8000}}
networks:
- app-networkNo need to restart Caddy or edit any configuration files — it auto-reloads!
- Docker Socket: Mounting
/var/run/docker.sockgives Caddy access to Docker events. Ensure your system is secured (e.g., restricted access to Docker). - Cloudflare Token: Use a scoped token with only DNS write permissions for
example.com.
- Memory: ~40–80MB baseline
- Performance: Sufficient for most microservices (3,750+ req/s)
- ARM Compatibility: Works on Raspberry Pi 3+ and newer ARM64 devices
- No Caddyfile needed — all config via environment variables and Docker labels.
- Fully declarative — everything defined in
docker-compose.yml. - Scalable — add new services with minimal effort.
- Secure — automatic HTTPS via DNS-01 with Cloudflare.
If you want to reset SSL certificates or force Caddy to re-fetch config:
docker-compose down -vThis removes persistent data (e.g., certificates), and a fresh setup will occur on next up.
This solution offers the best balance of simplicity, flexibility, and security for microservices on resource-constrained systems. It avoids vendor lock-in (unlike Cloudflare Tunnels), keeps memory usage low (unlike Traefik), and provides zero-config deployment with full HTTPS automation.