jufi.dev

project

caddyku

CLI that manages one shared Caddy reverse proxy for all the Docker projects on a VPS.

why this exists

I run several projects on one small VPS — this site, a staging environment for an exam platform, whatever I’m experimenting with that month. Every one of them is a Docker Compose project that needs a domain and HTTPS. The usual answers are either heavy (a separate nginx + certbot setup per app) or fragile: one shared reverse-proxy config you SSH into and hand-edit, where a single typo takes down every site on the box.

Caddy already solves the hard parts — automatic HTTPS via Let’s Encrypt, hot reload. What was missing was the workflow: adding and removing domains across many projects without ever touching the shared file by hand. caddyku is that workflow. Each app declares its domains in its own caddyku.yaml, and one command merges them safely into the shared Caddyfile.

and now it’s one command

ubuntu@vps · ~/projects
# the old way — ssh in and hand-edit the shared Caddyfile
vim ~/projects/caddy-proxy/Caddyfile # one typo = every site down
docker network connect caddy-net myapp-web
caddy reload # ...and hold your breath
# with caddyku — declare domains once, run one command
caddyku sync
Scanning /home/ubuntu/projects for caddyku.yaml files...
found: jufi.dev/caddyku.yaml (1 domain(s))
Syncing 1 project(s) into Caddyfile
Caddy reloaded successfully.

before / after

by hand
with caddyku
by hand A typo in the shared file → every site on the box is down until you fix it
with caddyku Validation fails inside the container, the previous config is restored, and the error names the block that broke
by hand A 502? Guess: container, network, or name — then go digging
with caddyku caddyku status walks the proxy → network → upstream chain and says which link broke
by hand Add a site = SSH in, edit config, wire the container onto the network, reload, hope
with caddyku caddyku init-app once, then caddyku add or sync
by hand See what’s routed where by reading the raw Caddyfile
with caddyku caddyku list shows project → domain → upstream

why it’s built this way

  • Never break the shared file

    caddyku only touches text between its own # BEGIN/END caddyku markers, so anything hand-written is preserved. Every write is validated inside the running Caddy container and rolled back on failure.

  • Explain failures, don’t hide them

    The common real-world problem is a silent 502, so caddyku status exists specifically to diagnose the container → network → upstream chain.

  • Don’t become a framework

    Simple things get YAML fields (redirect_www, basic_auth); anything more drops down to raw Caddy directives. Dependencies are kept to two.

  • Safe by default

    Every mutating command supports --dry-run to preview the exact Caddyfile diff before touching a production server.

live on this site

caddyku.yaml · the file that serves the page you’re reading
service: web
container: jufidev-web

domains:
  - domain: jufi.dev
    upstream: jufidev-web:80
    redirect_www: true

It’s open source (MIT), ships as a single Go binary with a self-update command and GoReleaser builds, and runs CI on every pull request. This site (jufi.dev) is served through it.

← all projects