Skip to content

API Deployment Overview

The Sema Link API (semalink-api) runs as a Dockerised Node.js application on a DigitalOcean droplet. Unlike the frontend, which is a static build uploaded to Cloudflare Pages, the API is a long-running server process that requires a real VM.

Environments

EnvironmentAPI DomainBranchDroplet
Stagingstaging-arc.semalink.africastaging209.38.197.79 (Frankfurt)
Testtest-arc.semalink.africatestTBD
Productionarc.semalink.africaprodTBD

All arc subdomains are proxied through Cloudflare (orange cloud) and protected by Cloudflare Zero Trust Access. Direct requests to the origin IP without a valid service token are rejected at the edge.

Stack Summary

LayerTechnologyNotes
RuntimeNode.js 22 (Alpine)CommonJS output from TypeScript
FrameworkFastify 5
Reverse proxyCaddy 2Handles HTTP/HTTPS on ports 80/443
Message queueRabbitMQ 3.13Runs as a local Docker container
DatabaseNeon (serverless Postgres)External — Frankfurt region, staging branch
CacheUpstash RedisExternal — Frankfurt region, TLS (rediss://)
ContainerisationDocker ComposeSingle docker-compose.staging.yml
CI/CDGitHub ActionsSSH deploy triggered on push to staging

How Secrets Are Managed

All environment variables are stored as GitHub Environment secrets under the staging environment in the semalink-api repository. On every deploy, the CI/CD workflow SSHes into the droplet and writes a fresh .env file from those secrets before restarting containers. The droplet itself holds no persistent secret state — it is always overwritten on deploy.

Service Architecture on the Droplet

Three Docker containers run in a single Compose project:

Internet (HTTPS)

Cloudflare Edge (TLS termination + Zero Trust)
    ↓  HTTP/HTTPS
  Caddy :80 / :443
    ↓  HTTP
  API (port 3000, internal only)
    ↓  AMQP
  RabbitMQ :5672
  • Caddy is the only container exposed to the host network on ports 80 and 443. It proxies all traffic to the api container on port 3000 using Docker's internal DNS (api:3000). Port 3000 is not bound to the host.
  • RabbitMQ is exposed on port 5672 (AMQP) and 15672 (management UI) for local debugging.
  • PostgreSQL and Redis are fully external — no containers for them.

Further Reading

Internal use only — Sema Link Engineering