Docker

CMSnap-LITE is a single fully static musl binary — FROM scratch works, no libc inside. A container needs the binary, a working directory and one volume for the state.

First run bootstraps everything

There is no separate container mode: the container simply runs /cms. Started with an empty working directory, the binary creates the root settings.json (bound to 0.0.0.0:8080 — reachable through the published port right away), this example site under www/default/, and prints the one-time first-run summary to stdout: the admin password, the /cms enrollment link (the admin zone is gate-hidden by default) and an MCP token. Read it with docker logs and open the enrollment link first.

Dockerfile skeleton

FROM scratch
WORKDIR /site
COPY cms /cms
VOLUME /site
EXPOSE 8080
USER 65534
CMD ["/cms"]

/site is the whole state: the root config, the sites with their databases, media, templates and TLS material, the node log under log/. Use a named volume so it survives container re-creation; the unprivileged user (65534) must be able to write it. To ship a prepared site instead of the bootstrap, set it up on the host (./cms def-help + your edits) and COPY --chown=65534:65534 site/ /site/.

Two deployment shapes

Port published directly (-p 8080:8080): docker bridge NAT masks every external client as the gateway IP. Keep trusted_proxies empty (the bootstrap default) — with the docker subnet trusted, anyone could spoof X-Real-IP and walk around rate limits.

Behind a proxy neighbour (nginx/caddy container on the same user-defined network, app port NOT published): add the proxy’s address or the network’s subnet to trusted_proxies in the root settings.json and make the proxy overwrite X-Real-IP (see the server article) — the app then sees real client addresses in logs and rate limits.

Notes

← All articles in this group