Complete websites from one settings.json: one binary — multi-site, CMS, JSON API, SQLite, HTTPS.
2.4K
The self-hosted Agentic Web Platform.
A complete website from one settings.json. One static ~60 MB binary
replaces the whole stack: web server, multi-site hosting, CMS admin panel,
public forms, JSON API, SQLite storage, request analytics (DuckDB) and
HTTPS with Let's Encrypt — nothing else to install, update or maintain.
An AI agent drives it over a built-in MCP endpoint: it writes the config,
the templates and the content, never backend code.
Measured: 1,042,959 requests per second on 16 server cores — with the request log on — and 10,000 full sites on one process at 5% cost (benchmark below).
Site & docs: https://cmsnap.sqliteonline.com
# 1. pull the image
docker pull sqliteonlinecom/cmsnap:latest
# 2. create the example site in a volume — the admin password is printed
# right here, in your terminal
docker run --rm -v mysite:/site sqliteonlinecom/cmsnap:latest /cmsnap def-help
# admin login: admin
# admin password: ................
# 3. start the server
docker run -d --name mysite -v mysite:/site -p 80:8080 sqliteonlinecom/cmsnap:latest
The example site is embedded in the binary — nothing is fetched from the network: a landing page, documentation stored in SQLite, a live contact form.
http://<host>/ — the site is up;http://<host>/cms — the admin panel; sign in with the login and
password from step 2, edit an article, refresh the page: the change is
live. Change the password right away.(Shortcut: step 2 is optional — started on an empty volume, the container
unpacks the example site itself; the password then lands in docker logs mysite.)
Bind-mount it and run as your own user — the container's default user (65534, unprivileged) cannot write to a folder owned by you:
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/mysite":/site sqliteonlinecom/cmsnap:latest /cmsnap def-help
docker run -d --name mysite --user "$(id -u):$(id -g)" -v "$PWD/mysite":/site -p 80:8080 sqliteonlinecom/cmsnap:latest
The whole site then sits in ./mysite as plain files you own — edit them
with anything, back them up like any folder. (Alternative for keeping the
unprivileged user: sudo chown 65534:65534 mysite and drop the --user
flag.)
Everything lives in the mysite volume: the root settings.json (process
settings and the domain → site map) plus one folder per site under www/,
each with its own settings.json (tables, views, routes, forms, the admin
menu), hbs/ templates, static/ and SQLite files in db/. Start from
the example and reshape it — edit anything, then apply with zero downtime:
docker exec mysite /cmsnap reload
One container hosts any number of sites: add a folder under www/ and a
domain → folder line to the root map, then /cmsnap reload — the new site is
live on its domain with its own admin users, databases and templates.
With native HTTPS each domain gets its own certificate via SNI.
Restarts never touch an existing site: with settings.json in the volume
the container just runs it. Want the example back? Factory reset (DELETES
your config and data): docker exec mysite /cmsnap def-help.
Measured on: CMSnap-LITE-M 0.2.2
Hardware: AMD EPYC 9554 (KVM, 32 physical cores / 64 threads, 128 GB RAM);
server pinned to 16 cores, wrk isolated on the other 16
Runs: 10 s warmup, 180 s measurement; access log ON (duckdb sink)
Cached page, 1 site: 1,042,959 req/s p50 435 µs (or 953,617 @ p50 89 µs)
Cached page, 10,000 sites: 987,228 req/s 10,000 full sites cost 5%
Page from SQLite, no cache: 255,302 req/s p50 416 µs (100 sites)
JSON API (~50 rows): 293,123 req/s p99 1.24 ms
Committed form writes: ~197,000/s into one db, 494,769/s across 100 dbs
Reproduce it all with one script: https://github.com/zirill/cmsnap. Full method and tables: https://cmsnap.sqliteonline.com/doc/28.
| Tag | Base | Purpose |
|---|---|---|
latest, <version> | scratch | production, nothing but the binary |
latest-alpine, <version>-alpine | alpine | debugging: shell for docker exec |
Every tag is a multi-arch manifest: amd64, arm64 (ARM servers,
Raspberry Pi 3+ with a 64-bit OS) and experimental riscv64 — docker pull picks your platform automatically. 64-bit only.
The image version always matches the Server: CMSnap-LITE/<version> header
the binary reports.
State: everything lives in the /site volume — config, templates,
static files, SQLite databases (db/), uploads (media/), logs, TLS
material. The container itself is disposable; --read-only root works
out of the box.
Ports: the app binds 0.0.0.0:8080 inside the container (it IS the
edge server — publish it directly with -p 80:8080). The in-container
user is unprivileged (65534), hence the non-privileged internal port.
HTTPS without a proxy: add the tls block to settings.json — your
PEM files, or Let's Encrypt certificates the binary obtains and renews
itself (publish both -p 80:8080 -p 443:8443-style mappings per your
config). Works in the scratch image: CA roots are compiled into the
binary, nothing is read from the OS.
Production flags: --restart unless-stopped survives crashes and
reboots; --cpus/--memory cap the container. One caveat with a memory
cap: the smart cache budgets itself from the HOST's free RAM (/proc/ meminfo — cgroup limits are invisible to it), so on a big host set
cache.mem_percent in settings.json low enough that the budget stays
under the cap.
--network host for busy sites: a published port (-p) goes through
docker's NAT/userland proxy, which costs about HALF the throughput at
high request rates. With --network host the engine binds the host
network directly (no -p needed), and behind a reverse proxy it can
bind loopback only (cmsnap tune proxy) — unreachable from outside, the
proxy is the single door:
docker run -d --name mysite -v mysite:/site --network host \
sqliteonlinecom/cmsnap:latest
Behind a reverse proxy: by default trusted_proxies is empty and all
IP headers are ignored (safe when the port is published directly). Behind
nginx/caddy, list the proxy's address/subnet in trusted_proxies so
X-Real-IP / X-Forwarded-Proto are honored. For nginx, use an upstream
with keepalive — without it nginx opens a new TCP connection to the app
for every request, capping throughput several times below what the engine
serves:
upstream cms_up { server 127.0.0.1:8080; keepalive 64; }
location / {
proxy_pass http://cms_up;
proxy_http_version 1.1;
proxy_pass_header Server;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
Proprietary, free to run: download and operate any number of sites — yours or your clients' — including commercially. Reselling the engine itself and reverse engineering are not permitted. Full texts ship inside the binary:
docker exec mysite /cmsnap license # the binary license agreement
docker exec mysite /cmsnap notices # third-party open-source notices
© Kirill N. CMSnap-LITE is built on permissively licensed open-source components (MIT/Apache-2.0/BSD and similar); no copyleft.
Up to and including 0.2.4 the binary inside the image was /cms; since
0.2.5 it is /cmsnap — the project's canonical name. On the older tags
keep using /cms in docker exec / docker run commands.
Content type
Image
Digest
sha256:6f9326d6a…
Size
22.5 MB
Last updated
about 1 month ago
docker pull sqliteonlinecom/cmsnap