Skip to content
Docs

Run the small box on Render

Simple Host on Render: your DNS points both names at the service; Render’s edge, behind Cloudflare, ends HTTPS and forwards to one web service running Caddy and the Simple Host server with a 1 GB disk; Render Postgres on the private network; your Git repository holds the Blueprint

The small box (the installer’s Postgres + server + Caddy) runs on Render as one web service with a disk, plus a Render Postgres database. Tested end to end on 2026-09-28 with v0.7.4, on a Starter service with a 1 GB disk and the smallest paid Postgres: first deploy (the Pre-Deploy Command loaded the schema and applied every migration), both custom domains with HTTPS, first admin and admin sign-in, a participant account, publish, update, rollback, saved data, the Caddy routes, /internal/* hidden, a redeploy (migrations: nothing to apply) and a restart with the site and data intact, and the visitor’s real address in visitor analytics (a spoofed X-Forwarded-For is ignored). Then everything was deleted. The service and database were created through Render’s API with the exact settings in render.yaml, and Render’s Blueprint validator accepts that file as it is.

The files are in deploy/platforms/render/: render.yaml (a Blueprint), a Dockerfile that copies the released server into the Caddy image, a Caddyfile (the small box’s, without its TLS parts), and start.sh. Nothing is compiled; no change to the server is needed.

What it looks like

Piece On a VPS (installer) On Render
HTTPS Caddy, on-demand certificates Render’s edge: a custom domain for each name
Sites served from disk, /internal/* hidden, access log Caddy container Caddy in the same service as the server, on Render’s port (10000)
Server app container same service, as a normal user, on 127.0.0.1:8090
Database db container Render Postgres, linked as DB_DSN (private network only)
Sites on disk Docker volume 1 GB Render disk at /data
Schema and migrations the installer runs simple-host migrate the Pre-Deploy Command loads schema.sql into an empty database, then runs simple-host migrate, before every deploy

Minimum size and cost

Prices from render.com/pricing on 2026-09-28. The server and Caddy used about 75 MB on Fly, so 512 MB is plenty. The test box used 16 MB of its 1 GB disk.

Item Size Per month
Web service Starter (0.5c-512mb), the smallest plan that takes a disk $7
Disk 1 GB, $0.25/GB $0.25
Postgres 0.1c-256mb $6
Postgres storage 1 GB, $0.30/GB $0.30
Custom domains and certificates free
Workspace Hobby $0
Total about $13.55

Why not free: a free web service has no disk, so published sites live in the container and are gone after a deploy, a restart, or the sleep that follows 15 minutes without traffic (seen in a free-plan test: after a redeploy the site was a 404 while its database rows were still there). Free services also have no Pre-Deploy Command, and a free Postgres expires after 30 days.

Commands

You need a Render account with a card, a GitHub (or GitLab) repository with these files (a fork of Simple Host, or your own repository with deploy/platforms/render/ copied in), a domain, and the release you want (this guide: 0.7.12, set in the Dockerfile as SIMPLE_HOST_VERSION). Below, the address is example.com, with participant sites on sites.example.com.

  1. Edit deploy/platforms/render/render.yaml: set the three hostnames (SITE_DOMAIN, CONTENT_HOST, PUBLIC_BASE_URL) and the two names under domains. Change region (both places) if Oregon is far from you. Commit and push.

  2. In the Render dashboard: New > Blueprint, pick the repository, and set Blueprint Path to deploy/platforms/render/render.yaml. Render creates the database, then builds the image and deploys the service. The Pre-Deploy Command loads the schema and applies every migration (empty database: loading schema.sql, applied 28 migration(s)) before the service starts.

  3. Point both names at the service. Its address is on the service’s page (simple-host-xxxx.onrender.com).

    example.com        CNAME  simple-host-xxxx.onrender.com
    sites.example.com  CNAME  simple-host-xxxx.onrender.com

    Render verifies each name and issues its certificate by itself; in the test both were live within a minute. (An apex name, example.com, takes an A record instead; the service’s Settings page shows it.)

  4. Sign in: the admin key is the service’s ADMIN_API_KEY (Environment tab, generated by the Blueprint). Open https://example.com/admin and paste it. From there, issue participant accounts (“Issue participant accounts”) and hand out their keys, as on any small box.

Everyday operations

  • Upgrade: set the new SIMPLE_HOST_VERSION in the Dockerfile and push. The Pre-Deploy Command runs the new release’s migrations first; if they fail, the deploy stops and the old version keeps running. A redeploy with nothing new logs database is up to date; nothing to apply. With a disk there is no zero-downtime deploy: the old instance stops before the new one starts. In the test, sites answered 502 for about 30 seconds.
  • Restart: Manual Deploy > Restart service on the service page. Sites, saved data and accounts all survive; about 30 seconds of 502 in the test.
  • Settings: anything in configuration.md goes in the service’s Environment tab. Email sign-in codes need RESEND_API_KEY and MAIL_FROM; without them participants use the keys the admin issues, which is how a small box runs by default.
  • Backups: Render snapshots the disk daily and keeps at least 7 days (the disk’s page restores one). Paid Postgres has point-in-time recovery (3 days on Hobby). “Download my data” and the admin export work as on any small box.
  • Delete everything: delete the Blueprint’s service and database in the dashboard (deleting the service takes its disk and custom domains with it), then remove the two DNS records.

Gotchas

  • A card is required. Without one, Render refuses the disk, the Pre-Deploy Command and paid Postgres (“Payment information is required to complete this request”). Do not try to run it on the free plan: sites vanish.
  • The admin page’s disk figure is the Render disk (973 MB usable on a 1 GB disk). Grow the disk on its page if it fills; it cannot shrink.
  • Health checks use your domain. Once the service has custom domains, Render sends its checks with one of them as the Host, which can be sites.<domain>. There the content routes answer 404, so the first deploy after adding the domains never went live and failed after 15 minutes. The Caddyfile sends /healthz to the server on every host.
  • Visitor addresses. Render’s proxy reaches Caddy over loopback (::1) and sits behind Cloudflare. The first X-Forwarded-For entry is whatever the visitor sent (a test with X-Forwarded-For: 6.6.6.6 came through as the visitor), so the Caddyfile reads CF-Connecting-IP, which Cloudflare sets and refuses (403) from a visitor, and sends the server that address alone in X-Forwarded-For. Without this, rate limits would share a few buckets (Render’s internal proxies) and analytics could be spoofed.
  • Caddy’s file capability. Render will not start a program that carries file capabilities (caddy: Operation not permitted), so the Dockerfile removes Caddy’s (it only lets Caddy bind ports below 1024).
  • Root and the disk. A new disk belongs to root, and in Render’s containers root cannot signal another user’s processes. start.sh hands /data to the server’s user and then runs everything as that user.
  • The release image cannot run alone. It is the server only. Sites are served from disk by Caddy, and Caddy also hides /internal/* and writes the access log that visitor analytics reads. Pointing Render straight at the server leaves every site a 404 and exposes /internal/*.
  • A new database is empty. simple-host migrate only adds to an existing schema. start.sh release loads db/schema.sql from the same release tag first, only when the users table is missing.
  • CMD, not ENTRYPOINT. The image starts with CMD, so the Pre-Deploy Command (/usr/local/bin/start.sh release) runs on its own rather than being handed to an entrypoint.

Your home page (2026-10-05): home selection, public showcase feed, bio, pins and manual order also work on small-box path installs. The public person page opens the selected site’s usual URL; the owner dashboard keeps its own origin. No whole-space domain is included. See Your home page.