Skip to content

Commit 24476db

Browse files
author
Tom Softreck
committed
caddy multiservice with caddy embedded on docker
1 parent 6f0b6a7 commit 24476db

File tree

9 files changed

+425
-0
lines changed

9 files changed

+425
-0
lines changed

caddy2/.env.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Domain configuration
2+
DOMAIN=example.com
3+
4+
# Cloudflare API Token (required for DNS-01 challenge)
5+
# Create one at: https://dash.cloudflare.com/profile/api-tokens
6+
# Required permissions: Zone.Zone:Read, Zone.DNS:Edit
7+
CF_API_TOKEN=your_cloudflare_api_token_here
8+
9+
# Subdomains for services
10+
API_SUBDOMAIN=api
11+
WEB_SUBDOMAIN=app
12+
AUTH_SUBDOMAIN=auth
13+
14+
# Email for Let's Encrypt notifications (optional but recommended)
15+

caddy2/.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Environment variables
2+
.env
3+
venv
4+
# Caddy data and config
5+
/data
6+
/config
7+
8+
# Docker
9+
Dockerfile
10+
.dockerignore
11+
12+
# IDE
13+
.vscode/
14+
.idea/
15+
16+
# OS generated files
17+
.DS_Store
18+
.DS_Store?
19+
._*
20+
.Spotlight-V100
21+
.Trashes
22+
ehthumbs.db
23+
Thumbs.db

caddy2/Makefile

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.PHONY: help up down restart logs status clean test test-setup
2+
3+
# Default target when just 'make' is run
4+
help:
5+
@echo "Available targets:"
6+
@echo " make up - Start all services in detached mode"
7+
@echo " make down - Stop and remove all containers"
8+
@echo " make restart - Restart all services"
9+
@echo " make logs - View logs from all services"
10+
@echo " make status - Show status of all containers"
11+
@echo " make clean - Remove all containers, networks, and volumes"
12+
@echo " make test - Run Ansible tests against the services"
13+
@echo " test-setup - Set up the test environment"
14+
15+
# Start all services in detached mode
16+
up:
17+
@if [ ! -f .env ]; then \
18+
echo "Creating .env file from .env.example"; \
19+
cp .env.example .env; \
20+
echo "Please edit .env with your configuration"; \
21+
exit 1; \
22+
fi
23+
docker-compose up -d
24+
25+
# Stop and remove all containers
26+
down:
27+
docker-compose down
28+
29+
# Restart all services
30+
restart: down up
31+
32+
# View logs from all services
33+
logs:
34+
docker-compose logs -f
35+
36+
# Show status of all containers
37+
status:
38+
docker-compose ps
39+
40+
# Remove all containers, networks, and volumes
41+
clean:
42+
docker-compose down -v --remove-orphans
43+
44+
# Set up test environment
45+
test-setup:
46+
python3 -m venv venv || true
47+
. venv/bin/activate && \
48+
pip install --upgrade pip && \
49+
pip install -r requirements-ansible.txt
50+
51+
# Run tests
52+
test:
53+
./run_tests.sh
54+
55+
# Create .env.example if it doesn't exist
56+
.env.example:
57+
@echo "DOMAIN=example.com" > .env.example
58+
@echo "CF_API_TOKEN=your_cloudflare_api_token" >> .env.example
59+
@echo "API_SUBDOMAIN=api" >> .env.example
60+
@echo "WEB_SUBDOMAIN=app" >> .env.example
61+
@echo "AUTH_SUBDOMAIN=auth" >> .env.example

caddy2/README.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
To integrate Caddy into your Docker Compose setup for dynamic subdomain routing without external configuration files, use the `caddy-docker-proxy` solution. This approach leverages Docker labels for service discovery and environment variables for global settings, eliminating the need for a standalone Caddyfile.
2+
3+
### ✅ Key Features
4+
- **Zero external config files** (no Caddyfile)
5+
- **Automatic HTTPS via Let's Encrypt + Cloudflare DNS**
6+
- **Dynamic service discovery** (add/remove services via Docker labels)
7+
- **ARM64 compatible**
8+
- **Single Docker Compose file**
9+
10+
---
11+
12+
### 🧩 Docker Compose Configuration
13+
14+
```yaml
15+
version: '3.8'
16+
17+
networks:
18+
app-network:
19+
driver: bridge
20+
21+
services:
22+
# Caddy Reverse Proxy with Docker Integration
23+
caddy:
24+
image: lucaslorentz/caddy-docker-proxy:latest
25+
environment:
26+
- CADDY_DOCKER_PROXY_ACME_DNS=cloudflare ${CF_API_TOKEN}
27+
28+
- DOMAIN=example.com
29+
volumes:
30+
- /var/run/docker.sock:/var/run/docker.sock
31+
ports:
32+
- "80:80"
33+
- "443:443"
34+
networks:
35+
- app-network
36+
37+
# Example Microservice 1: API Service
38+
api:
39+
image: your-api-image:latest
40+
labels:
41+
- caddy=${API_SUBDOMAIN}.${DOMAIN}
42+
- caddy.reverse_proxy={{upstreams 8080}}
43+
networks:
44+
- app-network
45+
46+
# Example Microservice 2: Web App
47+
web:
48+
image: your-web-app:latest
49+
labels:
50+
- caddy=${WEB_SUBDOMAIN}.${DOMAIN}
51+
- caddy.reverse_proxy={{upstreams 3000}}
52+
networks:
53+
- app-network
54+
55+
# Example Microservice 3: Auth Service
56+
auth:
57+
image: your-auth-service:latest
58+
labels:
59+
- caddy=${AUTH_SUBDOMAIN}.${DOMAIN}
60+
- caddy.reverse_proxy={{upstreams 4000}}
61+
networks:
62+
- app-network
63+
```
64+
65+
---
66+
67+
### 📁 Environment Variables (`.env` file)
68+
69+
```env
70+
DOMAIN=example.com
71+
CF_API_TOKEN=your_cloudflare_api_token
72+
API_SUBDOMAIN=api
73+
WEB_SUBDOMAIN=app
74+
AUTH_SUBDOMAIN=auth
75+
```
76+
77+
---
78+
79+
### 🔐 How It Works
80+
81+
- **Dynamic Configuration**: The `caddy-docker-proxy` image automatically detects containers with `caddy.*` labels.
82+
- **HTTPS Automation**: Uses Cloudflare DNS for Let's Encrypt challenges via the `CADDY_DOCKER_PROXY_ACME_DNS` environment variable.
83+
- **Service Discovery**: The `{{upstreams PORT}}` template routes traffic to the correct container based on exposed ports.
84+
85+
---
86+
87+
### 🧪 Adding New Services
88+
89+
To deploy a new service (e.g., `dashboard`), simply add it to your Docker Compose with the appropriate labels:
90+
91+
```yaml
92+
dashboard:
93+
image: your-dashboard:latest
94+
labels:
95+
- caddy=dashboard.example.com
96+
- caddy.reverse_proxy={{upstreams 8000}}
97+
networks:
98+
- app-network
99+
```
100+
101+
No need to restart Caddy or edit any configuration files — it auto-reloads!
102+
103+
---
104+
105+
### 🛡️ Security Notes
106+
107+
- **Docker Socket**: Mounting `/var/run/docker.sock` gives Caddy access to Docker events. Ensure your system is secured (e.g., restricted access to Docker).
108+
- **Cloudflare Token**: Use a **scoped token** with only DNS write permissions for `example.com`.
109+
110+
---
111+
112+
### 📦 Resource Usage
113+
114+
- **Memory**: ~40–80MB baseline
115+
- **Performance**: Sufficient for most microservices (3,750+ req/s)
116+
- **ARM Compatibility**: Works on Raspberry Pi 3+ and newer ARM64 devices
117+
118+
---
119+
120+
### ✅ Why This Works
121+
122+
- **No Caddyfile needed** — all config via environment variables and Docker labels.
123+
- **Fully declarative** — everything defined in `docker-compose.yml`.
124+
- **Scalable** — add new services with minimal effort.
125+
- **Secure** — automatic HTTPS via DNS-01 with Cloudflare.
126+
127+
---
128+
129+
### 🧼 Cleanup (Optional)
130+
131+
If you want to reset SSL certificates or force Caddy to re-fetch config:
132+
133+
```bash
134+
docker-compose down -v
135+
```
136+
137+
This removes persistent data (e.g., certificates), and a fresh setup will occur on next `up`.
138+
139+
---
140+
141+
### 📌 Summary
142+
143+
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.

caddy2/ansible_tests.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
- name: Test Caddy reverse proxy services
3+
hosts: localhost
4+
connection: local
5+
gather_facts: false
6+
vars_files:
7+
- .env
8+
9+
tasks:
10+
- name: Include test cases for each subdomain
11+
include_tasks: test_services.yml
12+
loop:
13+
- { name: "api", port: 80, path: "/" }
14+
- { name: "web", port: 80, path: "/" }
15+
- { name: "auth", port: 80, path: "/" }
16+
loop_control:
17+
loop_var: service
18+
19+
- name: Test HTTPS redirect
20+
uri:
21+
url: "http://{{ DOMAIN }}"
22+
method: GET
23+
follow_redirects: none
24+
status_code: 301, 302
25+
timeout: 10
26+
register: http_redirect
27+
failed_when: false
28+
29+
- name: Verify HTTPS redirect
30+
assert:
31+
that:
32+
- "'https://' in http_redirect.redirected_url"
33+
- "http_redirect.redirected_url == 'https://' ~ DOMAIN ~ '/'"
34+
fail_msg: "HTTPS redirect failed"
35+
36+
- name: Test HTTP/2 support
37+
uri:
38+
url: "https://{{ DOMAIN }}"
39+
method: GET
40+
validate_certs: no
41+
timeout: 10
42+
register: http2_test
43+
failed_when: false
44+
45+
- name: Verify HTTP/2 support
46+
assert:
47+
that:
48+
- "http2_test.status == 200"
49+
- "'HTTP/2' in http2_test.msg"
50+
fail_msg: "HTTP/2 not supported"

caddy2/docker-compose.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
version: '3.8'
2+
3+
networks:
4+
app-network:
5+
driver: bridge
6+
7+
services:
8+
# Caddy Reverse Proxy with Docker Integration
9+
caddy:
10+
image: lucaslorentz/caddy-docker-proxy:latest
11+
environment:
12+
- CADDY_DOCKER_PROXY_ACME_DNS=cloudflare ${CF_API_TOKEN}
13+
- CADDY_DOCKER_PROXY_ACME_EMAIL=admin@${DOMAIN}
14+
- DOMAIN=${DOMAIN}
15+
volumes:
16+
- /var/run/docker.sock:/var/run/docker.sock
17+
- caddy_data:/data
18+
- caddy_config:/config
19+
ports:
20+
- "80:80"
21+
- "443:443"
22+
restart: unless-stopped
23+
networks:
24+
- app-network
25+
26+
# Example API Service
27+
api:
28+
image: nginx:alpine
29+
labels:
30+
- caddy=${API_SUBDOMAIN}.${DOMAIN}
31+
- caddy.reverse_proxy={{upstreams 80}}
32+
networks:
33+
- app-network
34+
35+
# Example Web App
36+
web:
37+
image: nginx:alpine
38+
labels:
39+
- caddy=${WEB_SUBDOMAIN}.${DOMAIN}
40+
- caddy.reverse_proxy={{upstreams 80}}
41+
networks:
42+
- app-network
43+
44+
# Example Auth Service
45+
auth:
46+
image: nginx:alpine
47+
labels:
48+
- caddy=${AUTH_SUBDOMAIN}.${DOMAIN}
49+
- caddy.reverse_proxy={{upstreams 80}}
50+
networks:
51+
- app-network
52+
53+
volumes:
54+
caddy_data:
55+
caddy_config:

caddy2/requirements-ansible.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ansible>=2.9
2+
requests>=2.25.0

caddy2/run_tests.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Create virtual environment if it doesn't exist
5+
if [ ! -d "venv" ]; then
6+
echo "Creating virtual environment..."
7+
python3 -m venv venv
8+
source venv/bin/activate
9+
pip install --upgrade pip
10+
pip install -r requirements-ansible.txt
11+
else
12+
source venv/bin/activate
13+
fi
14+
15+
# Run Ansible tests
16+
echo "Running Ansible tests..."
17+
ansible-playbook ansible_tests.yml -i "localhost," -c local -v
18+
19+
# Check the result and exit with appropriate status
20+
if [ $? -eq 0 ]; then
21+
echo "✅ All tests passed successfully!"
22+
exit 0
23+
else
24+
echo "❌ Some tests failed!"
25+
exit 1
26+
fi

0 commit comments

Comments
 (0)