Skip to content

Tech Story: VPS baseline provisioning (Nginx, Certbot, deploy user) #107

@GitAddRemote

Description

@GitAddRemote

Tech Story

As a platform engineer, I want the Linode VPS configured with a reverse proxy, automatic TLS certificates, a least-privilege deploy user, and a clean directory structure so that Station can be deployed securely without running as root or exposing unencrypted traffic.

ELI5 Context

What is Nginx (as a reverse proxy)? Nginx sits in front of your apps like a receptionist. When a request arrives for api.drdnt.org, Nginx says "that's the NestJS backend" and forwards it. It also handles HTTPS (the padlock) so your NestJS app doesn't have to worry about SSL at all — it just speaks plain HTTP internally.

What is Let's Encrypt / Certbot? Let's Encrypt is a free service that issues SSL certificates (what makes https:// work). Without a certificate, browsers show a scary "not secure" warning and all traffic is sent in plain text — anyone on the same network can read it. Certbot is the tool that automatically requests the certificate and renews it before it expires (certificates last 90 days). You set it up once and forget about it.

Why a deploy user instead of root? Root can do anything on the server — delete the OS, read every file, etc. If your deployment process is ever compromised (e.g. a malicious GitHub Actions run), root access = total server compromise. A deploy user can only do what it needs: run Docker commands and restart services. Principle of least privilege.

What is a swap file? RAM is fast but limited (2GB). A swap file is disk space that pretends to be RAM when you run out. It's slow (disk vs RAM), but it prevents your server from crashing when memory spikes. Think of it as an emergency overflow bucket.

Technical Elaboration

New files (in infra/scripts/)

infra/scripts/bootstrap-vps.sh
A one-time setup script to run manually on the VPS after it's provisioned. Documents and automates:

  1. Update system packages (apt update && apt upgrade -y)
  2. Install Docker + Docker Compose plugin (official Docker repo)
  3. Install Nginx
  4. Install Certbot + Nginx plugin (certbot python3-certbot-nginx)
  5. Create deploy user: useradd -m -s /bin/bash deploy
  6. Add deploy to docker group (so it can run Docker without sudo)
  7. Add SSH public key to ~deploy/.ssh/authorized_keys
  8. Create /opt/station/ directory, set ownership to deploy
  9. Create 2GB swap file at /swapfile (prevents OOM kills on 2GB RAM)

infra/nginx/api.drdnt.org.conf

server {
    listen 80;
    server_name api.drdnt.org;
    # Certbot will add the HTTPS block after cert issuance
    location / {
        proxy_pass http://localhost:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

infra/nginx/station.drdnt.org.conf
Proxies to the React frontend container (port 5173 or nginx:80 inside Docker).

infra/nginx/bot.drdnt.org.conf
Reserved — proxies to station-bot health endpoint (port TBD). Placeholder config.

infra/scripts/issue-certs.sh
Documents the one-time Certbot command:

certbot --nginx -d api.drdnt.org -d station.drdnt.org -d bot.drdnt.org

Certbot edits the Nginx configs to add HTTPS + auto-redirect from HTTP.

infra/scripts/setup-swap.sh

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab  # persist across reboots

infra/README.md (update from #106)
Add a "VPS Baseline" section with step-by-step instructions referencing these scripts.

Directory structure on VPS after setup

/opt/station/
  docker-compose.prod.yml   (deployed by CI)
  .env.production           (set manually once, never in git)
  logs/

Definition of Done

  • infra/scripts/bootstrap-vps.sh written and tested on the VPS
  • Nginx installed and running; configs in place for all three subdomains
  • Certbot issued certs for api.drdnt.org, station.drdnt.org, bot.drdnt.org; HTTPS works and HTTP redirects to HTTPS
  • Certbot auto-renewal confirmed: certbot renew --dry-run passes
  • deploy user exists, is in docker group, can SSH with deploy key, cannot sudo
  • /opt/station/ exists, owned by deploy
  • 2GB swap file active (free -h shows swap)
  • All three subdomains resolve in browser with valid TLS (no cert warnings)
  • Scripts committed to infra/scripts/ with inline comments explaining each step

Dependencies

Metadata

Metadata

Assignees

Labels

backendBackend services and logicconfigConfiguration and feature flagssecuritySecurity, auth, and permissionstech-storyTechnical implementation story

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions