Sign inSign up

sqliteonlinecom/cmsnap

By sqliteonlinecom

•Updated about 1 month ago

Complete websites from one settings.json: one binary — multi-site, CMS, JSON API, SQLite, HTTPS.

Image
0

2.4K

sqliteonlinecom/cmsnap repository overview

⁠CMSnap-LITE

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⁠

⁠Quick start: the example site

# 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.

  1. Open it:
    • 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.)

⁠Prefer a host folder over a named volume?

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.)

⁠Your own site

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 performance

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⁠.

⁠Tags

TagBasePurpose
latest, <version>scratchproduction, nothing but the binary
latest-alpine, <version>-alpinealpinedebugging: 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.

⁠Deployment notes

  • 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;
    }
    

⁠License

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.

⁠Binary name change

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.

Tag summary

Content type

Image

Digest

sha256:6f9326d6a…

Size

22.5 MB

Last updated

about 1 month ago

docker pull sqliteonlinecom/cmsnap