Incus Caddy Config

Dynamic, zero-touch reverse proxy configuration for Caddy on Incus, driven by instance lifecycle events.

caddy-config watches your Incus fleet via the ievent stream, renders Caddyfiles from instance labels in real time, validates them in-container, and deploys them directly into persistent storage volumes via Incus SFTP.

flowchart TD
    Incus["Incus API (Events & Control)"] -->|instance events| Chain["ievent Chain<br/>(source &rarr; debounce &rarr; enricher)"]
    Chain --> Plugin["caddy-config Plugin"]

    subgraph Storage["Incus Storage Pool"]
        Vol[("custom/caddy-config volume<br/>mounted at /config")]
    end

    subgraph Caddy["Serving Caddy Container"]
        CaddyProc["caddy:2.11.4-alpine<br/>(admin localhost:2019)"]
    end

    Plugin -->|"1. SFTP write (.Caddyfile.tmp)"| Vol
    Plugin -->|"2. Incus exec ('caddy validate')"| CaddyProc
    Plugin -->|"3. SFTP atomic rename (.Caddyfile.tmp &rarr; Caddyfile)"| Vol
    Plugin -->|"4. Incus exec ('caddy reload')"| CaddyProc

Quick Example

Define Caddy and your backend application in compose.yaml:

services:
  caddy:
    image: docker.io/library/caddy:2.11.4-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - caddy-config:/config
      - caddy-data:/data

  api:
    image: docker.io/library/busybox:latest
    command: httpd -f -p 8080
    labels:
      edge.domain: "api.example.com"
      edge.upstream: "8080"

volumes:
  caddy-config:
  caddy-data:

When caddy-config runs with --caddy-instance edge:default:caddy, it automatically discovers api, resolves its Incus bridge IPv4 address, and renders the reverse proxy block into Caddy:

{
	admin localhost:2019
}

api.example.com {
	reverse_proxy 10.0.1.42:8080
}

When you scale, stop, start, or rename instances, Caddy reloads within milliseconds of the Incus event.


Key Highlights


Documentation Navigation