Running with Docker¶
Getting started takes you through the first start. This page is the reference for the compose deployment.
Requirements: Docker 20.10+ with Compose v2. Docker sets net.ipv4.ip_unprivileged_port_start=0 in the container's
network namespace, so the image's non-root user can bind 80/443; the apphost also carries cap_net_bind_service for
runtimes that do not.
- Ports: 80/tcp (HTTP, ACME HTTP-01, redirects), 443/tcp (HTTP/1.1 and HTTP/2) and 443/udp (HTTP/3), and 9443/tcp (the admin app).
- Data: one directory, mounted at
/datain the container. It holdspaduraksa.db(config, users, audit),vault.db(certificate keys, secrets, the Data Protection key ring),last-good.json, the admin socket underrun/, the logs underlogs/and, unless you supply the master key another way,master.key. - Master key: the one key that opens
vault.db. See The master key.
The compose file¶
deploy/compose.yaml runs ghcr.io/hizkifw/paduraksa:main, which CI publishes from every commit on main that passes, for amd64 and arm64.
Once there is a release, a tag such as :0.1 will name it. master.key is the vault's master key, mounted as a Docker secret so that it is not in the data volume; compose refuses to start without the file.
To build the image yourself, run compose from a checkout's deploy/ directory with the commented build: block
enabled (its context is the repository root, one directory up), and start it with docker compose up -d --build.
Or build once with docker build -t paduraksa:local . in the repository root and set image: paduraksa:local.
A bind-mounted data directory (instead of the named volume) must be owned by the container's uid 10001:
sudo install -d -o 10001 -g 10001 -m 0750 ./data
# compose: - ./data:/data
Ports and capabilities. The default bridge network works because Docker sets the unprivileged-port floor to 0. The
compose file runs the container hardened: a read-only root filesystem with a tmpfs /tmp, no-new-privileges, and
cap_drop: [ALL] with cap_add: [NET_BIND_SERVICE] back. Keep that one capability if you change it, or exec of the
setcap'd apphost fails with EPERM. In Kubernetes "restricted", add
securityContext.capabilities.add: ["NET_BIND_SERVICE"], or set the pod sysctl
net.ipv4.ip_unprivileged_port_start=0.
Do not remap the container's ports: Alt-Svc and the HTTP→HTTPS redirect advertise 443, because that is the port
clients use. Behind a port translation, set Paduraksa__Listen__PublicHttpsPort to the outside port. Map 443/udp too: it is HTTP/3, and
without it browsers fall back to HTTP/2 over 443/tcp (see HTTP/3).
The compose file sets a stable hostname, so the admin certificate's machine name does not change when the container is recreated.
It also sets stop_grace_period: 35s, which gives Kestrel its 30-second drain, and a json-file log driver that keeps 5 files of 10 MB, so the setup-token banner stays readable in docker logs.
Where things live¶
The named volume paduraksa-data holds everything:
| Path | What |
|---|---|
/data/paduraksa.db |
config, revisions, users, sessions, audit chain (SQLite, WAL) |
/data/vault.db |
sealed certificate keys, ACME/DNS secrets, TOTP secrets, the Data Protection key ring |
/data/master.key |
the KEK, only when you did not set PADURAKSA_MASTER_KEY_FILE or PADURAKSA_MASTER_KEY |
/data/last-good.json |
the last live config, used for a degraded start |
/data/setup-token |
until the first Owner exists |
/data/run/admin.sock |
the internal listener the public proxy uses for an admin host |
/data/run/server.lock |
held by the running server, so a second server (or a restore) finds the directory taken |
/data/logs/ |
access.log and error.log (0700 directory, 0600 files), with rotated, gzip-compressed older files |
Back up the data directory and the master key separately: see Backups and restore.
Settings¶
Settings are environment variables in the compose file (Paduraksa__Listen__PublicHttpsPort=...). Every one of them, and how a list is written, is in Server settings.