Skip to content
Docs

Storage and backups

A server keeps everything in two places:

  • Postgres: accounts, keys, sites, versions, saved data and lists, domains.
  • A folder on disk (DATA_DIR): every site’s files, one folder per kept version.

On a small box both are Docker volumes (simple-host_db and simple-host_sites), and /opt/simple-host/.env holds the admin key and the database password, which only that file knows.

Setting Default Allowed What it does
VISITOR_AI_WRITES_PER_DAY 200 1–100000 writes Visitor connector write attempts per person and site per UTC day, shared across apps and restarts. Security-sensitive.
SITE_STORAGE_FILES_MAX_OBJECTS 1000 1–100000 objects Maximum committed file objects per bucket, including empty files; overwrites do not add an object.
SITE_STORAGE_SQL_CONCURRENCY 0 0–1024 queries Concurrent SQLite executions across the process; defaults to the CPU count.
SITE_STORAGE_ACQUIRE_WAIT_MS 250 1–5000 milliseconds Wait for a SQLite execution slot before returning 503 with Retry-After.
SITE_STORAGE_WRITE_LOCK_WAIT_MS 2000 1–10000 milliseconds Wait for the site write/deploy lock before returning 503 with Retry-After.
SITE_STORAGE_OWNER_TIMEOUT_MS 5000 1–30000 milliseconds Owner SQLite query, schema and write timeout.
SITE_STORAGE_VISITOR_WRITE_TIMEOUT_MS 2000 1–5000 milliseconds Visitor storage write timeout.
SITE_STORAGE_VISITOR_QUERY_TIMEOUT_MS 1000 1–5000 milliseconds Visitor SQLite query timeout.
RATE_LIMIT_STORAGE_IP 120,100ms any (warns past 10× looser) Visitor storage writes per site and client IP; owner credentials are exempt.
RATE_LIMIT_STORAGE_VISITOR 60,200ms any (warns past 10× looser) Storage writes per site and signed-in visitor; limits add-only flooding.
DB_DSN none secret The Postgres connection string. install.sh sets it for the bundled database. Required. Security-sensitive.
DB_MAX_OPEN_CONNS 25 at least 1 connections Maximum open Postgres connections per server process; excess requests wait.
DB_MAX_IDLE_CONNS 5 at least 0 connections Maximum idle Postgres connections per server process; zero keeps none.
DB_CONN_MAX_LIFETIME_MINUTES 30 at least 1 minutes Maximum lifetime of each pooled Postgres connection.
DB_CONN_MAX_IDLE_MINUTES 5 at least 1 minutes Maximum idle time of each pooled Postgres connection.
MIGRATE_ON_START false true / false true makes the server build an empty database from the embedded schema.sql and apply every pending migration before it listens (the same as simple-host migrate -init). For Kubernetes, where there is no psql and no install command; two pods starting at once are safe (an advisory lock).
DATA_DIR ./data/sites text The folder that holds every site’s files and versions.
SITE_STORAGE_FILE_MAX_BYTES 1000000 1–10737418240 bytes Largest individual raw-file upload for a site storage bucket.
SITE_STORAGE_SQL_RESULT_MAX_BYTES 1000000 1–67108864 bytes Largest JSON result from one site SQLite query.

Recipes

Back up a small box (from /opt/simple-host, into the current folder):

sudo docker compose exec -T db pg_dump -U simplehost simplehost | gzip > db-$(date +%F).sql.gz && sudo docker run --rm -v simple-host_sites:/sites -v "$PWD":/out alpine tar czf /out/sites-$(date +%F).tar.gz -C /sites . && sudo cp .env env-$(date +%F).bak

Keep all three together: the .env backup is what lets a restored database be opened.

What fills the disk. At the default allowances, 2,000 fully used hosted sites can add 40 GB of logical KV/SQLite and file storage (10 MB + 10 MB per site), beyond deployed versions. Budget extra for filesystem metadata, Postgres, SQLite WAL, backups and version retention. Storage quotas are shared within a site; visitor rate limits slow filling them but do not reserve space for other visitors.

For deployed versions: set KEEP_VERSIONS (see Sites and versions). The admin page shows disk use.

Postgres client pool

DB_DSN selects the database. Each server process bounds its own database/sql pool. Go clamps DB_MAX_IDLE_CONNS to the open limit.

Unset or empty values use these defaults; invalid values stop startup with the variable name. The open limit of 25 is sized for two instances of this binary sharing one 100-connection Postgres role, leaving headroom for other services on the box. These are client settings; they do not change Postgres server configuration. Set them in each instance’s environment and restart that instance to apply them.

Storage execution settings

SITE_STORAGE_SQL_CONCURRENCY=0 uses the CPU count. See the table above for slot waits, write/deploy-lock waits and owner/visitor execution timeouts. The durable allowance excludes deployment versions, legacy saved data and transient SQLite WAL. The website file allowance still applies to each raw-file upload.

Without TRUSTED_PROXY_CIDRS the client address is the last X-Forwarded-For entry from any peer. Keep the app behind a local proxy that appends the transport client address and restrict direct access to the app port; an unproxied install set up that way has no trustworthy IP write limit. With TRUSTED_PROXY_CIDRS set (Kubernetes, a load balancer in front), the header is believed only from those networks and a direct connection from anywhere else is its own client.

Kubernetes sets trusted proxy networks to the cluster pod and node ranges; see Kubernetes. The same client address controls the country gate.