MCPcopy Index your code
hub / github.com/lucaslorentz/caddy-docker-proxy

github.com/lucaslorentz/caddy-docker-proxy @v2.13.1 sqlite

repository ↗ · DeepWiki ↗ · release v2.13.1 ↗
197 symbols 611 edges 34 files 76 documented · 39%
README

Caddy-Docker-Proxy

Build Status Go Report Card

Introduction

This plugin enables Caddy to be used as a reverse proxy for Docker containers via labels.

How does it work?

The plugin scans Docker metadata, looking for labels indicating that the service or container should be served by Caddy.

Then, it generates an in-memory Caddyfile with site entries and proxies pointing to each Docker service by their DNS name or container IP.

Every time a Docker object changes, the plugin updates the Caddyfile and triggers Caddy to gracefully reload, with zero-downtime.

Table of contents

Basic usage example (Docker Compose)

$ docker network create caddy --ipv6

[!NOTE] The --ipv6 flag instructs Docker to assign IPv6 addresses for all containers connected to this network. Without this flag, Caddy (as well as any upstream services) will see Docker's gateway IP address instead of the actual client IP addresses for IPv6 clients.

caddy/compose.yaml

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    ports:
      - 80:80
      - 443:443/tcp
      - 443:443/udp
    environment:
      - CADDY_INGRESS_NETWORKS=caddy
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
    restart: unless-stopped

networks:
  caddy:
    external: true

volumes:
  caddy_data: {}
$ docker compose up -d

whoami/compose.yaml

services:
  whoami:
    image: traefik/whoami
    networks:
      - caddy
    labels:
      caddy: whoami.example.com
      caddy.reverse_proxy: "{{upstreams 80}}"

networks:
  caddy:
    external: true
$ docker compose up -d

Now, visit https://whoami.example.com. The site will be served automatically over HTTPS with a certificate issued by Let's Encrypt or ZeroSSL.

Labels to Caddyfile conversion

Please first read the Caddyfile Concepts documentation to understand the structure of a Caddyfile.

Any label prefixed with caddy will be converted into a Caddyfile config, following these rules:

Tokens and arguments

Keys are the directive name, and values are whitespace separated arguments:

caddy.directive: arg1 arg2
↓
{
    directive arg1 arg2
}

If you need whitespace or line-breaks inside one of the arguments, use double-quotes or backticks around it:

caddy.respond: / "Hello World" 200
↓
{
    respond / "Hello World" 200
}
caddy.respond: / `Hello\nWorld` 200
↓
{
    respond / `Hello
World` 200
}
caddy.respond: |
    / `Hello
    World` 200
↓
{
    respond / `Hello
World` 200
}

Dots represent nesting, and grouping is done automatically:

caddy.directive: argA  
caddy.directive.subdirA: valueA  
caddy.directive.subdirB: valueB1 valueB2
↓
{
    directive argA {  
        subdirA valueA  
        subdirB valueB1 valueB2  
    }
}

Arguments for the parent directive are optional (e.g. no arguments to directive, setting subdirective subdirA directly):

caddy.directive.subdirA: valueA
↓
{
    directive {
        subdirA valueA
    }
}

Labels with empty values generate a directive without any arguments:

caddy.directive:
↓
{
    directive
}

Ordering and isolation

Be aware that directives are subject to be sorted according to the default directive order defined by Caddy, when the Caddyfile is parsed (after the Caddyfile is generated from labels).

Directives from labels are ordered alphabetically by default:

caddy.bbb: value
caddy.aaa: value
↓
{
    aaa value 
    bbb value
}

Suffix _<number> isolates directives that otherwise would be grouped:

caddy.route_0.a: value
caddy.route_1.b: value
↓
{
    route {
        a value
    }
    route {
        b value
    }
}

Prefix <number>_ isolates directives but also defines a custom ordering for directives (mainly relevant within route blocks), and directives without order prefix will go last:

caddy.1_bbb: value
caddy.2_aaa: value
caddy.3_aaa: value
↓
{
    bbb value
    aaa value
    aaa value
}

Sites, snippets and global options

A label caddy creates a site block:

caddy: example.com
caddy.respond: "Hello World" 200
↓
example.com {
    respond "Hello World" 200
}

Or a snippet:

caddy: (encode)
caddy.encode: zstd gzip
↓
(encode) {
    encode zstd gzip
}

It's also possible to isolate Caddy configurations using suffix _<number>:

caddy_0: (snippet)
caddy_0.tls: internal
caddy_1: site-a.com
caddy_1.import: snippet
caddy_2: site-b.com
caddy_2.import: snippet
↓
(snippet) {
    tls internal
}
site_a {
    import snippet
}
site_b {
    import snippet
}

Global options can be defined by not setting any value for caddy. They can be set in any container/service, including caddy-docker-proxy itself. Here is an example

caddy.email: you@example.com
↓
{
    email you@example.com
}

Named matchers can be created using @ inside labels:

caddy: localhost
caddy.@match.path: /sourcepath /sourcepath/*
caddy.reverse_proxy: @match localhost:6001
↓
localhost {
    @match {
        path /sourcepath /sourcepath/*
    }
    reverse_proxy @match localhost:6001
}

Go templates

Golang templates can be used inside label values to increase flexibility. From templates, you have access to current Docker resource information. But, keep in mind that the structure that describes a Docker container is different from a service.

While you can access a service name like this:

caddy.respond: /info "{{.Spec.Name}}"
↓
respond /info "myservice"

The equivalent to access a container name would be:

caddy.respond: /info "{{index .Names 0}}"
↓
respond /info "mycontainer"

Sometimes it's not possile to have labels with empty values, like when using some UI to manage Docker. If that's the case, you can also use our support for go lang templates to generate empty labels.

caddy.directive: {{""}}
↓
directive

Template functions

The following functions are available for use inside templates:

upstreams

Returns all addresses for the current Docker resource separated by whitespace.

For services, that would be the service DNS name when proxy-service-tasks is false, or all running tasks IPs when proxy-service-tasks is true.

For containers, that would be the container IPs.

Only containers/services that are connected to Caddy ingress networks are used.

:warning: caddy docker proxy does a best effort to automatically detect what are the ingress networks. But that logic fails on some scenarios: #207. To have a more resilient solution, you can manually configure Caddy ingress network using CLI option ingress-networks, environment variable CADDY_INGRESS_NETWORKS. You can also specify the ingress network per container/service by adding to it a label caddy_ingress_network with the network name.

Usage: upstreams [http|https] [port]

Examples:

caddy.reverse_proxy: {{upstreams}}
↓
reverse_proxy 192.168.0.1 192.168.0.2
caddy.reverse_proxy: {{upstreams https}}
↓
reverse_proxy https://192.168.0.1 https://192.168.0.2
caddy.reverse_proxy: {{upstreams 8080}}
↓
reverse_proxy 192.168.0.1:8080 192.168.0.2:8080
caddy.reverse_proxy: {{upstreams http 8080}}
↓
reverse_proxy http://192.168.0.1:8080 http://192.168.0.2:8080

:warning: Be carefull with quotes around upstreams. Quotes should only be added when using yaml.

caddy.reverse_proxy: "{{upstreams}}"
↓
reverse_proxy "192.168.0.1 192.168.0.2"

Examples

Proxying all requests to a domain to the container

caddy: example.com
caddy.reverse_proxy: {{upstreams}}

Proxying all requests to a domain to a subpath in the container

caddy: example.com
caddy.rewrite: * /target{path}
caddy.reverse_proxy: {{upstreams}}

Proxying requests matching a path

caddy: example.com
caddy.handle: /source/*
caddy.handle.0_reverse_proxy: {{upstreams}}

Proxying requests matching a path, while stripping that path prefix

caddy: example.com
caddy.handle_path: /source/*
caddy.handle_path.0_reverse_proxy: {{upstreams}}

Proxying requests matching a path, rewriting to different path prefix

caddy: example.com
caddy.handle_path: /source/*
caddy.handle_path.0_rewrite: * /target{uri}
caddy.handle_path.1_reverse_proxy: {{upstreams}}

Proxying all websocket requests, and all requests to /api*, to the container

caddy: example.com
caddy.@ws.0_header: Connection *Upgrade*
caddy.@ws.1_header: Upgrade websocket
caddy.0_reverse_proxy: @ws {{upstreams}}
caddy.1_reverse_proxy: /api* {{upstreams}}

Proxying multiple domains, with certificates for each

caddy: example.com, example.org, www.example.com, www.example.org
caddy.reverse_proxy: {{upstreams}}

Redirecting

caddy: example.com
caddy.redir_0: /favicon.ico  /alternative/icon.ico 302
caddy.redir_1: /photo.png    /updated-photo.png    302

More community-maintained examples are available in the Wiki.

Docker configs

Note: This is for Docker Swarm only. Alternatively, use CADDY_DOCKER_CADDYFILE_PATH or -caddyfile-path

You can also add raw text to your Caddyfile using Docker configs. Just add Caddy label prefix to your configs and the whole config content will be inserted at the beginning of the generated Caddyfile, outside any server blocks.

Here is an example

Proxying services vs containers

Caddy docker proxy is able to proxy to swarm services or raw containers. Both features are always enabled, and what will differentiate the proxy target is where you define your labels.

Services

To proxy swarm services, labels should be defined at service level. With your compose.yaml, labels should be inside deploy, like:

services:
  foo:
    deploy: # <-- labels should be _inside_ `deploy`
      labels:
        caddy: service.example.com
        caddy.reverse_proxy: {{upstreams}}

Caddy will use service DNS name as target or all service tasks IPs, depending on configuration proxy-service-tasks.

Containers

To proxy containers, labels should be defined at container level. With your compose.yaml, labels should be outside deploy, like:

services:
  foo:
    labels:
      caddy: service.example.com
      caddy.reverse_proxy: {{upstreams}}

Execution modes

Each caddy docker proxy instance can be executed in one of the following modes.

Server

Acts as a proxy to your Docker resources. The server starts without any configuration, and will not serve anything until it is configured by a "controller".

In order to make a server discoverable and configurable by controllers, you need to mark it with label caddy_controlled_server and define the controller network via CLI option controller-network or environment variable CADDY_CONTROLLER_NETWORK.

Server instances doesn't need access to Docker host socket and you can run it in manager or worker nodes.

Configuration example

Controller

Controller monitors your Docker cluster, generates Caddy configuration, and pushes it to all servers it finds in your Docker cluster.

When controller instances are connected to more than one network, it is also necessary to define the controller network via CLI option controller-network or environment variable CADDY_CONTROLLER_NETWORK.

Controller instances require access to Docker host socket.

A single controller instance can configure all server instances in your cluster.

**:warning: Controller mode requires server nodes to serve

Extension points exported contracts — how you extend this code

Utils (Interface)
Utils is an interface with docker utilities [2 implementers]
docker/utils.go
Client (Interface)
Client is an interface with needed functionalities from docker client [2 implementers]
docker/client.go

Core symbols most depended-on inside this repo

Marshal
called by 16
caddyfile/marshal.go
prepareServerConfig
called by 14
loader.go
Info
called by 11
docker/client.go
buildCaddyLoggingConfig
called by 10
cmd.go
extractContainerIDFromCGroups
called by 9
docker/utils.go
logger
called by 8
loader.go
Merge
called by 6
caddyfile/merge.go
CreateContainer
called by 6
caddyfile/caddyfile.go

Shape

Function 103
Method 76
Struct 14
Interface 2
FuncType 1
TypeAlias 1

Languages

Go100%

Modules by API surface

docker/client.go23 symbols
docker/client_mock.go12 symbols
caddyfile/caddyfile.go12 symbols
generator/services_test.go11 symbols
generator/containers_test.go11 symbols
loader.go10 symbols
generator/generator_test.go10 symbols
caddyfile/marshal.go10 symbols
docker/utils_test.go9 symbols
docker/utils.go9 symbols
cmd.go9 symbols
generator/generator.go6 symbols

Dependencies from manifests, versioned

cel.dev/exprv0.25.1 · 1×
cloud.google.com/go/auth/oauth2adaptv0.2.8 · 1×
cloud.google.com/go/compute/metadatav0.9.0 · 1×
dario.cat/mergov1.0.2 · 1×
filippo.io/bigmodv0.1.0 · 1×
filippo.io/edwards25519v1.2.0 · 1×
github.com/AndreasBriese/bbloomv0.0.0-2019082515265 · 1×
github.com/DeRuina/timberjackv1.4.2 · 1×
github.com/KimMachineGun/automemlimitv0.7.5 · 1×
github.com/Masterminds/goutilsv1.1.1 · 1×

For agents

$ claude mcp add caddy-docker-proxy \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact