Skip to content

Deployment

BlocMarket’s primary production path runs the API as a static MUSL binary under systemd on Linux, with nginx terminating TLS and serving the Leptos WASM frontend.

Client (browser/mobile)
|
v
nginx (443) -- TLS termination
|
+-- api.blocmarket.org --> 127.0.0.1:8090 (bloc-api)
+-- app.blocmarket.org --> /opt/blocmarket/web/dist (static files)
ComponentTechnology
APIStatic MUSL binary (bloc-api) under systemd
WebLeptos WASM frontend served by nginx
DatabasePostgreSQL 16 + PostGIS 3.4 (Supabase or local)
CacheRedis 7
Terminal window
cd /opt/blocmarket/deploy/systemd
sudo bash install.sh

This installs the blocmarket-api.service unit and creates the dedicated service user.

The deploy script in deploy/ builds the release binary and transfers it to the server:

Terminal window
./deploy.sh # Build and deploy
./deploy.sh --build-only # Build only (no remote transfer)
./deploy.sh --restart-only # Restart service only (no rebuild)

The script automatically backs up the previous binary before uploading the new one.

All runtime configuration lives in /opt/blocmarket/.env, read by the systemd service via EnvironmentFile=:

Terminal window
ssh <server>
sudo nano /opt/blocmarket/.env
sudo systemctl restart blocmarket-api
VariableDescription
API_PORTPort the API binds to (default: 8090)
DATABASE_URLPostgreSQL connection string
REDIS_URLRedis connection URL
JWT_SECRETToken signing secret (min 32 chars)
CORS_ALLOWED_ORIGINSComma-separated allowed origins
RUST_LOGLog level filter
Terminal window
# Status
sudo systemctl status blocmarket-api
# Logs
sudo journalctl -u blocmarket-api -f # Live tail
sudo journalctl -u blocmarket-api -n 100 # Last 100 lines
sudo journalctl -u blocmarket-api -b # Since last boot
# Restart / stop / start
sudo systemctl restart blocmarket-api
sudo systemctl stop blocmarket-api
sudo systemctl start blocmarket-api

The deploy script keeps timestamped backups of previous binaries:

Terminal window
# List backups
ssh <server> 'ls -la /opt/blocmarket/bin/bloc-api.bak.*'
# Roll back to a previous version
ssh <server>
sudo cp /opt/blocmarket/bin/bloc-api.bak.YYYYMMDDHHMMSS /opt/blocmarket/bin/bloc-api
sudo systemctl restart blocmarket-api
Terminal window
sudo apt install nginx
# Install config
sudo cp deploy/nginx/blocmarket.conf /etc/nginx/sites-available/blocmarket.conf
sudo ln -sf /etc/nginx/sites-available/blocmarket.conf /etc/nginx/sites-enabled/blocmarket.conf
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Terminal window
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api-blocmarket.wyattau.com -d app.blocmarket.wyattau.com

Certbot modifies the nginx config to add TLS. Certificates auto-renew via the certbot systemd timer. HTTP is redirected to HTTPS with a 301.

The service unit applies these restrictions:

  • Runs as dedicated blocmarket user (no home, no shell)
  • ProtectSystem=strict with explicit ReadWritePaths
  • NoNewPrivileges, PrivateTmp, MemoryDenyWriteExecute
  • Only CAP_NET_BIND_SERVICE capability
  • ProtectKernelTunables, ProtectKernelModules, ProtectControlGroups
  • File descriptor limit: 65536

A production compose file is available at deploy/docker-compose.prod.yml for containerised single-node deployments. The API container maps port 3000; local development uses 8090.

Kustomize bases and overlays live in deploy/k8s/ (with dev and prod overlays), and a Helm chart in deploy/helm/blocmarket/ including deployment, service, ingress, and HPA templates:

Terminal window
# Kustomize
kubectl apply -k deploy/k8s/overlays/prod
# Helm
helm upgrade --install blocmarket deploy/helm/blocmarket --values deploy/helm/blocmarket/values.yaml

deploy/blue-green.sh supports zero-downtime cutover between two service generations for environments that require it.

Terminal window
# Health endpoints (all must return 200)
curl -sf https://api-blocmarket.wyattau.com/health
curl -sf https://api-blocmarket.wyattau.com/health/db
curl -sf https://api-blocmarket.wyattau.com/health/redis
curl -sf https://api-blocmarket.wyattau.com/ready
# Security headers
curl -sI https://api-blocmarket.wyattau.com/health | \
grep -iE 'strict-transport|x-frame|x-content-type|content-security|referrer-policy'
# TLS certificate
echo | openssl s_client -servername api-blocmarket.wyattau.com \
-connect api-blocmarket.wyattau.com:443 2>/dev/null | \
openssl x509 -noout -subject -dates -issuer
  • JWT_SECRET is 32+ chars and unique per environment
  • DATABASE_URL uses TLS (Supabase requires SSL)
  • CORS_ALLOWED_ORIGINS excludes * and localhost
  • Rate limiting is enabled (Redis reachable)
  • TLS certificates valid and auto-renewing
  • Security headers enforced
  • Metrics endpoint restricted to the internal network