Deployment Patterns
This guide covers deployment patterns beyond Docker. For the Docker path, see deployment.html and dev-docs/DOCKER_DEVELOPMENT.adoc. Reference configs live in resources/deploy/.
Prerequisites
Build the uberjar before any deployment:
clojure -T:build clean && clojure -T:build uber
# Produces target/wagoe-*.jar
Run migrations before starting the server:
clojure -M:migrate up
The jar has no migrate command.
Environment Variables
All deployments need these variables. See resources/deploy/systemd/wagoe.env.example for a full template.
| Variable | Required | Description |
|---|---|---|
|
Yes |
|
|
Yes |
32+ character signing secret |
|
Yes |
Database hostname |
|
No |
Database port (default 5432) |
|
Yes |
Database name |
|
Yes |
Database user |
|
Yes |
Database password |
|
No |
Read by the dev and test profiles only; prod listens on 3000 |
|
Yes, unless |
The prod profile reports errors to Sentry and refuses to boot without a DSN |
|
No |
Required if cache/jobs enabled |
|
No |
Redis auth password |
systemd (bare metal / VM)
Reference files: resources/deploy/systemd/
Setup
# 1. Create service user
sudo useradd -r -s /usr/sbin/nologin wagoe
# 2. Deploy the jar
sudo mkdir -p /opt/wagoe/{logs,data}
sudo cp target/wagoe-*.jar /opt/wagoe/wagoe.jar
sudo chown -R wagoe:wagoe /opt/wagoe
# 3. Configure secrets
sudo mkdir -p /etc/wagoe
sudo cp resources/deploy/systemd/wagoe.env.example /etc/wagoe/wagoe.env
sudo chmod 600 /etc/wagoe/wagoe.env
# Edit /etc/wagoe/wagoe.env with real values
# 4. Install and start the service
sudo cp resources/deploy/systemd/wagoe.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now wagoe
Operations
sudo systemctl status wagoe # Check status
sudo journalctl -u wagoe -f # Tail logs
sudo systemctl restart wagoe # Restart after deploy
The unit file does not migrate: run clojure -M:migrate up before each deploy. On failure it restarts after 10 seconds, with a maximum of 5 retries per minute.
JVM Tuning
The unit file sets default JVM options. Adjust for your server:
# In wagoe.service — set heap to ~70% of available RAM
Environment=JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
| Server RAM | Recommended -Xmx |
|---|---|
1 GB |
512m |
2 GB |
1g |
4 GB |
2g-3g |
8 GB+ |
4g-6g |
nginx Reverse Proxy
Reference file: resources/deploy/nginx/wagoe.conf
# 1. Install
sudo cp resources/deploy/nginx/wagoe.conf /etc/nginx/sites-available/wagoe
sudo ln -s /etc/nginx/sites-available/wagoe /etc/nginx/sites-enabled/
# 2. Replace your-domain.com with your actual domain
# 3. TLS (Let's Encrypt)
sudo certbot --nginx -d your-domain.com
# 4. Test and reload
sudo nginx -t && sudo systemctl reload nginx
The config includes:
-
HTTP → HTTPS redirect
-
WebSocket upgrade support for
/ws(realtime library) -
Suppressed access logs on
/health -
Connection keepalive to the app
Client address behind a proxy
Behind a proxy the TCP peer is the proxy. Rate limiting, login risk checks, sessions
and the audit log then see every user as one address. List the proxies whose
X-Forwarded-For you trust:
:wagoe/http {:trusted-proxies ["127.0.0.1/32" "10.0.0.0/8" "fd00::/8"]}
When the peer is in one of these ranges, the client is the right-most
X-Forwarded-For hop that is not. Otherwise the header is ignored, since any
client can send it. The default is empty: trust nothing. Entries are IPv4 or
IPv6 CIDRs, or bare addresses; an invalid one stops the boot. Handlers read the
result through wagoe.platform.core.http.client-ip/request-ip.
If the hop where the walk stops is not an address, as when a proxy writes
unknown, the client is nil rather than the proxy. Code that grants access by
address must refuse nil; client-ip/in-any? returns false for it. Rate limiting
puts these requests in one bucket per proxy, separate from named clients.
Cloud Platforms
Fly.io
Reference file: resources/deploy/cloud/fly.toml
# 1. Install flyctl: https://fly.io/docs/getting-started/installing-flyctl/
# 2. Launch (first time)
fly launch --copy-config
# 3. Create database
fly postgres create --name my-wagoe-app-db
fly postgres attach my-wagoe-app-db
# 4. Set secrets
fly secrets set JWT_SECRET="your-secret-32-chars-min" POSTGRES_PASSWORD="..."
# 5. Deploy
fly deploy
The fly.toml configures:
-
Auto-scaling (min 1 machine, auto-stop idle)
-
Health checks against
/health/ready -
HTTPS enforcement
-
Amsterdam region (adjust
primary_regionas needed)
Render
Reference file: resources/deploy/cloud/render.yaml
-
Push to GitHub
-
Connect the repo on https://dashboard.render.com
-
Select the
render.yamlBlueprint -
Render auto-provisions PostgreSQL and wires environment variables
The blueprint configures:
-
Web service + managed PostgreSQL
-
Auto-generated
JWT_SECRET -
Health checks against
/health/ready -
Frankfurt region
Health Check Integration
All deployment patterns should use the health endpoints:
| Endpoint | Purpose | Use for |
|---|---|---|
|
Basic liveness + service info |
Monitoring dashboards |
|
Dependency checks (DB, cache) |
Load balancer readiness probes, deploy gates |
|
Process alive |
Container orchestrator liveness probes |
/health/ready returns 503 when any dependency is unreachable, making it safe for load balancer health checks - traffic is not routed until the database is connected.