Synsema docsENES

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

FlagEffect
--port NOverrides serve on N and grants serve(N).
--domain d1,d2ACME SAN domains.
--tls-auto <email>Automatic HTTPS (ACME) — this is the dev↔prod switch; needs a domain.
--tls-cert / --tls-keyManual 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.

Runtime knobs → the process environment (not .env). SYNSEMA_SERVE_WORKERS=N sizes the pool of interpreter workers that handle requests (default: one per core, min 2). Raise it for I/O-bound handlers — more requests in flight, at the cost of more RAM. Unlike app values, this knob configures the runtime itself, so it is read once, from the process environment, before the first server startsEnvironment= under systemd, -e in Docker, export in a shell. Putting it in .env does nothing: .env only feeds env()/secret() inside your program.

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

The ACME challenge listens on port 80 (HTTP-01), which the CA must reach from the public

internet. If something else already owns that port — another server, a container mapping,

a sidecar — move it with SYNSEMA_ACME_HTTP_PORT=8080 and forward external :80 to it.

Like the other runtime knobs it is read from the process environment, not .env.

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

[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

Docker & Kubernetes

The image is just the Synsema binary — the same prebuilt static binary the site installs. The Dockerfile in the repo fetches it from the GitHub release and verifies its checksum (it does not compile); you mount your .syn program into /app:

docker build -t synsema .                                   # or --build-arg SYNSEMA_VERSION=v0.4.9
docker run -d --restart unless-stopped -e ANTHROPIC_API_KEY=sk-... \
    -v "$PWD":/app -w /app -p 8080:8080 synsema serve app.syn

In K8s, command: ["synsema", "serve", "/app/app.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.