-
Notifications
You must be signed in to change notification settings - Fork 15
Installation
CaddyUI is one container (or one binary) that talks to Caddy over the admin API. Caddy itself runs separately: in the same Compose stack, on the same host, or on remote "edge" nodes reached over a private network.
- Docker Compose (recommended)
- Custom Caddy build for DNS-01, CrowdSec and Gandi
- Docker run
- MariaDB backend
- systemd binary install
- Agent mode: edge-only Caddy nodes
- Image tags
- Upgrading
services:
caddy:
image: caddy:2-alpine
container_name: caddyui-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- caddy_data:/data
- caddy_config:/config
environment:
CADDY_ADMIN: 0.0.0.0:2019
command: >-
mkdir -p /config/caddy;
[ -f /config/caddy/autosave.json ] || echo '{}' > /config/caddy/autosave.json;
exec caddy run --config /config/caddy/autosave.json --resume --adapter json
networks:
- caddyui
caddyui:
image: applegater/caddyui:latest
container_name: caddyui
restart: unless-stopped
depends_on:
- caddy
ports:
- "8081:8080"
volumes:
- caddyui_data:/data
environment:
CADDYUI_DB: /data/caddyui.db
CADDYUI_LISTEN: :8080
CADDY_ADMIN_URL: http://caddy:2019
networks:
- caddyui
volumes:
caddy_data:
caddy_config:
caddyui_data:
networks:
caddyui:
driver: bridgeOpen http://localhost:8081 and create the first administrator account. See Configuration for what happens next.
Why the command block? Caddy normally loads only its Caddyfile at boot and forgets anything pushed to the admin API. --resume makes it boot from autosave.json, the last config it received, so CaddyUI's pushes survive a docker compose restart. On a fresh install that file does not exist yet and Caddy would crash-loop, so the command seeds an empty {} config the first time.
Port 2019 is not published in this stack. CaddyUI reaches it over the private Compose network as http://caddy:2019. Keep it that way: the admin API has no authentication and whoever reaches it controls Caddy. See Security and Users for the options when Caddy runs elsewhere.
The repository's own docker-compose.yml is the same stack with the custom Caddy build below, a TZ variable shared by both containers, a persisted /var/log/caddy volume for file access logs, and commented examples for mounting certificate directories and the Caddy data volume.
The stock caddy:2-alpine image has no DNS provider modules, so it cannot solve ACME DNS-01 challenges (wildcard certificates, hosts that are not reachable on port 80) and has no CrowdSec bouncer. The repository ships Dockerfile.caddy, which builds Caddy with every DNS provider CaddyUI supports (Cloudflare, Porkbun, Namecheap, GoDaddy, DigitalOcean, Hetzner, Route 53, Gandi) and the CrowdSec HTTP bouncer.
caddy:
build:
context: .
dockerfile: Dockerfile.caddy
image: caddyui-caddy:latestUse it whenever you plan to use Managed DNS certificates, DNS Providers or the CrowdSec integration. Plain HTTP-01 proxy hosts work fine with the stock image.
docker run -d \
--name caddyui \
-p 8081:8080 \
-v caddyui_data:/data \
-e CADDY_ADMIN_URL=http://your-caddy-host:2019 \
applegater/caddyui:latestBind mounts: if you mount a host directory to /data instead of a named volume, it must be owned by uid 10001, the non-root user the container runs as. The container prints a clear error at start-up when the directory is not writable.
sudo chown 10001:10001 /path/to/caddyui_dataSQLite is the zero-configuration default and right for most installs. MariaDB is available for larger installations that need concurrent UI reads, continuous analytics ingestion, replication or platform-managed backups.
With the included overlay:
export CADDYUI_MARIADB_PASSWORD='replace-with-a-long-random-password'
export CADDYUI_MARIADB_ROOT_PASSWORD='replace-with-a-different-random-password'
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -dAgainst an existing MariaDB server:
environment:
CADDYUI_DB_DRIVER: mariadb
CADDYUI_DB_DSN: caddyui:password@tcp(mariadb.example.internal:3306)/caddyuiCreate the database and user first. CaddyUI creates and migrates its tables automatically and never writes the DSN to its logs.
Take a timestamped copy of caddyui.db, stop the normal CaddyUI container, start the empty MariaDB service and run the migration command:
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -d mariadb
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml run --rm --no-deps \
caddyui migrate-db \
--from-sqlite /data/caddyui.db
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -dThe SQLite file is opened read-only and the MariaDB destination must be empty. Add --skip-analytics to leave the large access_events and access_daily tables behind, which is strongly recommended when the SQLite file grew mostly because of visitor analytics.
MariaDB backups are your database platform's job (mariadb-dump, snapshots, point-in-time recovery). Caddy configuration snapshots inside CaddyUI work on both backends.
Tagged GitHub releases include linux/amd64 and linux/arm64 tarballs with the caddyui binary, a sample caddyui.service unit and an installer. This suits LXC/Proxmox labs, VMs, or any host where you prefer no Docker.
- Install Caddy separately and make sure its admin API is reachable from the CaddyUI host. On the same host the default is
http://127.0.0.1:2019. - Download the archive for your architecture from the latest release.
- Extract and run the installer:
tar -xvf ./caddyui_vX.Y.Z_linux_ARCH.tar.gz
cd caddyui_vX.Y.Z_linux_ARCH
./install.shThe packaged service stores SQLite data at /var/lib/caddyui/caddyui.db, listens on 127.0.0.1:8080, talks to Caddy at http://127.0.0.1:2019 and binds the log-ingest listener to 127.0.0.1:9019. Override with a drop-in:
sudo systemctl edit caddyui[Service]
Environment=CADDYUI_LISTEN=0.0.0.0:8080
Environment=CADDY_ADMIN_URL=http://10.8.0.2:2019
# Optional: disable the ingest listener if you do not use analytics, certificate
# lifecycle status or Server Logs.
Environment=CADDYUI_INGEST_LISTEN=sudo systemctl restart caddyuiBuilding the binary yourself is covered in Development and Releases.
For several hosts you need one CaddyUI. Every other host runs only Caddy, and the central CaddyUI manages it through the admin API, usually over WireGuard or Tailscale. No database, no UI container and no extra public port on the edge.
On each edge host:
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
# --resume is required so admin-API pushes persist across Caddy restarts.
command: >-
mkdir -p /config/caddy;
[ -f /config/caddy/autosave.json ] || echo '{}' > /config/caddy/autosave.json;
exec caddy run --config /config/caddy/autosave.json --resume --adapter json
ports:
# Bind the admin API to your private tunnel IP (WireGuard / Tailscale).
# Do NOT expose :2019 on a public interface.
- "10.8.0.2:2019:2019"
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:Then in the central CaddyUI open Caddy Fleet → Add server and point it at http://10.8.0.2:2019. Give the node its own Log ingest target (the CaddyUI host as that node can reach it, for example 10.8.0.1:9019) so analytics, certificate status and Server Logs work for it too. See Caddy Fleet.
| Tag | Points at |
|---|---|
:vX.Y.Z |
A specific release, immutable. Recommended for production. |
:stable |
The current release. Updated on every release. |
:latest |
Kept in lockstep with :stable. |
All three resolve to the same image on release day. Images are multi-arch (linux/amd64, linux/arm64), built from scratch, and run as non-root uid 10001. The old rolling :preview tag is retired and no longer updated.
- Pull the new tag.
- Recreate the container. Portainer: Recreate with Re-pull image enabled. CLI:
docker compose pull && docker compose up -d. - Database migrations run automatically at start-up; the log lists each one applied.
- Read the CHANGELOG entry for anything that needs attention.
Downgrading is not supported once a newer version has migrated the database. Take a backup first (see Snapshots and Backup).
CaddyUI · Changelog · Docker Hub · Report an issue — never expose Caddy's admin port 2019 to the internet.
Getting started
Routing
Operations
Integrations
Help