When I first got serious about self-hosting and spun up a bunch of services, I quickly realized that I needed a reverse proxy (it took me some Ducking to realize what a reverse proxy was) so that I could host multiple services without needing to use weird ports. And at that time I chose Caddy v1 because it had a simple configuration spec and automatic HTTPS. As I was migrating to hosting everything in Docker, I kept seeing Traefik recommended but didn't like that it seemed more complicated than Caddy to me. I did, however, like that Traefik allows for defining rules in Docker Compose right alongside the rest of your configuration, so I began to search around for alternatives.
I quickly stumbled upon Caddy-Docker-Proxy and knew it was just what I was looking for. It makes setting up a basic reverse proxy rule a breeze, but allows for the full power of Caddy for services that require a bit beyond the basics. On top of that, it constantly monitors for changes to docker labels so no restarts are needed to pick up changes. To give you a taste of its power and simplicity, let me give some examples (pretty much straight from my personal docker-compose.yml
.
Define the caddy container:
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:2.3
ports:
- 80:80
- 443:443
networks:
- caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
deploy:
placement:
constraints:
- node.role == manager
replicas: 1
restart_policy:
condition: any
labels:
caddy.email: my-email@example.com
A basic reverse proxy:
miniflux:
image: miniflux/miniflux:latest
restart: always
ports:
- 8014:8080
networks:
- caddy
- internal
depends_on:
- postgres
environment:
- DATABASE_URL=postgres://miniflux:${MINIFLUX_DB_PASSWORD}@postgres/miniflux?sslmode=disable
# ...
labels:
caddy: rss.example.com
caddy.reverse_proxy: "{{upstreams 8080}}"
Equivalent in a Caddyfile:
rss.example.com {
reverse_proxy 172.XXX.XXX.XXX:8080
}
The only gotcha here is to make sure to use the port that the service is running on in the container, not the port you expose to the host. So in this case, Miniflux is running on port 8080 in the container but is exposed to the host machine on port 8014 (for internal testing purposes), so I tell caddy to point to the container's IP on port 8080. {{upstreams}}
is a helper provided by caddy-docker-proxy to get the container's IP within the caddy
Docker network.
A more complex example:
nextcloud:
image: nextcloud:latest
restart: always
networks:
- caddy
- internal
ports:
- 8001:80
depends_on:
- mariadb
- redis
volumes:
- nextcloud_data:/var/www/html/data
- ./apps/nextcloud/config:/var/www/html/config
- nextcloud_apps:/var/www/html/apps
environment:
- MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=mariadb
- REDIS_HOST=redis
- TRUSTED_PROXIES=172.XXX.XXX.XXX/24 # this is the IP range of my caddy network
- APACHE_DISABLE_REWRITE_IP=1
labels:
caddy: nextcloud.example.com
caddy.reverse_proxy: "{{upstreams 80}}"
caddy.0_redir: "/.well-known/carddav /remote.php/dav 301"
caddy.1_redir: "/.well-known/caldav /remote.php/dav 301"
caddy.header: "Strict-Transport-Security max-age=15552000"
Equivalent in a Caddyfile:
nextcloud.example.com {
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
header Strict-Transport-Security max-age=15552000
reverse_proxy 172.XXX.XXX.XXX:80
}
Interestingly, the Nextcloud documentation now includes a Caddy v2 example, but when I set up my configuration I based the rules off the nginx example. The only weird thing about this is the number prefixes before the redir
directives. This prevents them from grouping together and also orders them (although it doesn't matter in this case).
Local-only access
Lets say you want the benefits of automatic HTTPS, but don't actually want a service to be accessible from outside your network. Caddy allows for only matched requests to be proxied:
local-only-service:
image: asdfasdf:latest
labels:
caddy: local-only.example.com
caddy.@local.remote_ip: 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8
caddy.reverse_proxy: "@local {{upstreams 80}}"
Equivalent in a Caddyfile:
local-only.example.com {
@local {
remote_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8
}
reverse_proxy @local 172.XXX.XXX.XXX:80
}
These examples should cover many of your potential use cases, and there's always the Caddy community forum for any questions on more complex configurations. If you have any helper containers that you can't live without, I would love to hear about them in the comments.
Top comments (0)