Caddy Docker Proxy, Like Traefik But Better?
Tags: caddy, docker, reverseproxy, selfhosted
Published: 2021-09-24 05:36:58

---
title: Caddy Docker Proxy, Like Traefik But Better?
published: true
description: Caddy Docker Proxy is a reverse proxy that looks at your Docker labels to build an equivalent Caddyfile. I give a few examples of its awesomeness.
tags: caddy,docker,reverseproxy,selfhosted
//cover_image: https://direct_url_to_image.jpg
---

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](https://duckduckgo.com/?q=reverse+proxy) 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](https://caddyserver.com/v2) v1 because it had a simple configuration spec and automatic HTTPS. As I was migrating to hosting everything in Docker, I kept seeing [Traefik](https://doc.traefik.io/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](https://docs.docker.com/compose/) right alongside the rest of your configuration, so I began to search around for alternatives.

I quickly stumbled upon [Caddy-Docker-Proxy](https://github.com/lucaslorentz/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:

```yaml
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:

```yaml
  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](https://miniflux.app/) 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:

```yaml
  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](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/reverse_proxy_configuration.html?highlight=reverse%20proxy#caddy) 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](https://caddyserver.com/docs/caddyfile/matchers) requests to be proxied:

```yaml
  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](https://caddy.community/) 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.