Deploy
Synsema ships as a single static binary — no runtime on the target. The serve block stays dev-clean in the repo; deployment knobs are CLI flags, so the same file runs locally and in prod without edits.
synsema serve app.syn # dev: :8080, plain HTTP, no setup
synsema serve app.syn --port 443 --domain example.com,www.example.com --tls-auto admin@example.com # prod: HTTPS
Serve flags
| Flag | Effect |
|---|---|
--port N | Overrides serve on N and grants serve(N). |
--domain d1,d2 | ACME SAN domains. |
--tls-auto <email> | Automatic HTTPS (ACME) — this is the dev↔prod switch; needs a domain. |
--tls-cert / --tls-key | Manual TLS (mutually exclusive with --tls-auto). |
--bind <addr> | Bind address (default 0.0.0.0). |
Precedence: CLI flag > file clause > default. No --tls-auto → plain HTTP (dev); --tls-auto → TLS (prod).
Per-environment config (keep the repo dev-clean)
The .syn file never changes between your laptop and the server, so git pull on prod never conflicts. Everything that differs lives outside the code.
Server knobs → CLI flags. --port, --domain, --tls-auto (table above). The flag wins over the file clause.
App values → the environment. Anything you read with env("NAME", default) — e.g. the canonical public URL that feeds your canonical/OG/sitemap tags:
require env("SITE_URL")
let SITE be env("SITE_URL", "http://127.0.0.1:8080") -- dev default; prod overrides
Set it on prod in the environment (Environment=SITE_URL=https://example.com under systemd, -e SITE_URL=… in Docker). Resolution is process env > .env > code default, so no repo edit is needed.
Two apps on one host? Give each its own port — the code stays identical: synsema serve api.syn --port 8081 and synsema serve admin.syn --port 8082. Since --port overrides serve on N and grants serve(N), nothing in either file changes.
HTTPS, step by step (free, auto-renewing cert)
--tls-auto gets a free certificate from Let's Encrypt (ACME) and auto-renews it — no certbot, no cron. What you need:
1. A domain pointing to the server's IP (a DNS A/AAAA record).
2. Ports 80 and 443 reachable — port 80 answers the one-time ACME challenge, then redirects to 443.
3. Run with the domain + a contact email:
synsema serve app.syn --port 443 --domain example.com,www.example.com --tls-auto you@example.com
On first boot Synsema obtains the cert, serves HTTPS on 443, redirects HTTP→HTTPS, and auto-renews ~30 days before the 90-day expiry. Certs are stored (SYNSEMA_CERT_DIR → ~/.synsema/certs), so a restart reloads them (no re-issue → no rate-limit).
Already have a cert? Use --tls-cert cert.pem --tls-key key.pem instead (mutually exclusive with --tls-auto). Under systemd, give the service a writable HOME/StateDirectory (below) so it can store certs.
synsema daemon vs systemd — pick one
synsema daemon start app.syn— built-in background manager (status/logs/stop/restart). No OS config, but no boot-start, no crash-restart. Good for dev / no-systemd boxes.- systemd — OS supervisor: boot-start (
enable),Restart=always, journald logs. Use for production.
[Service]
ExecStart=/usr/local/bin/synsema serve /opt/app/app.syn --port 443 --domain example.com --tls-auto admin@example.com
Restart=always
StateDirectory=synsema # writable HOME for ~/.synsema/certs fallback
Multiple sites on one host (Synsema is its own edge proxy)
Two processes can't both bind :443, and you don't need nginx/Caddy. One Synsema process is the edge: it terminates TLS for every domain (one SAN cert) and routes by Host to each backend, which runs plain-HTTP on a private port.
-- edge.syn — TLS + Host routing for every site on the box
require serve(443)
require net("127.0.0.1") -- deny-by-default: the edge only talks to localhost
serve on 443
host "example.com"
route "GET /" -- root: /*path does NOT match "/"
proxy to "http://127.0.0.1:8080"
route "GET /*path"
proxy to "http://127.0.0.1:8080"
route "POST /*path"
proxy to "http://127.0.0.1:8080"
host "docs.example.com"
route "GET /"
proxy to "http://127.0.0.1:8791"
route "GET /*path"
proxy to "http://127.0.0.1:8791"
route "POST /*path"
proxy to "http://127.0.0.1:8791"
Run the edge with a SAN cert for all domains; each backend runs plain-HTTP, localhost-only, with its own repo/version/service:
synsema serve edge.syn --port 443 --domain example.com,docs.example.com --tls-auto admin@example.com
synsema serve app.syn --port 8080 --bind 127.0.0.1
synsema serve docs.syn --port 8791 --bind 127.0.0.1
- Root gotcha:
route "GET /*path"needs ≥1 segment — it does not match/. Addroute "GET /"too (per method) so the home page reaches the backend. - Per method:
routebinds method+path — declare each method you forward (GET, POST, …). proxy toforwards status + content-type + body and the upstream's end-to-end headers (Location,Set-Cookie,Cache-Control,ETag, …), so redirects, cookies and caching work through the edge; hop-by-hop are dropped.- Independent deploys: restart one backend without touching the others; each can run its own Synsema version behind the same edge.
Docker & Kubernetes
docker run -d --restart unless-stopped -e ANTHROPIC_API_KEY=sk-... synsema serve app.syn
In K8s, command: ["synsema", "serve", "/app/agent.syn"] and inject keys via secretKeyRef. Secrets/config come from the environment in prod (overrides .env) — see Secrets.
Updating
A running server does not auto-update. synsema update swaps the binary on disk; systemctl restart applies it. TLS certs persist (stored + auto-renewed), so a restart reloads them — no Let's Encrypt rate-limit hit.