Skip to content

Infrastructure Overview

Everything runs on DigitalOcean: a managed Kubernetes cluster for the services, a managed PostgreSQL cluster, Spaces with a CDN for media, a private container registry, and one dedicated droplet for LiveKit. Infrastructure is defined in Terraform; workloads in plain Kubernetes manifests.

The shape of it

Rendering diagram…

Components

ComponentTypeSpec
KubernetesDOKS1.34.5-do.6, 3 nodes s-4vcpu-8gb, autoscaling 2–10
DatabaseManaged PostgreSQL15, db-s-2vcpu-4gb, 1 node
RedisIn-clusterinfra/k8s/redis-deployment.yaml, 1 replica
Object storageSpaces + CDNplaypalz-media, CORS configured
RegistryDO Container Registryregistry.digitalocean.com/playpalzproduction, basic tier
LiveKitDedicated droplets-4vcpu-8gb, provisioned by Ansible
Ingressnginx-ingress (Helm)Fronted by a DO Load Balancer
TLScert-manager (Helm)Let's Encrypt, auto-renewing
LoggingLoki + Grafana (Helm)grafana.playpalz.gg
NetworkVPC10.10.0.0/16

Redis is a single in-cluster pod

Redis is not a managed service here — it runs as one replica inside the cluster. It backs the media queue, the Socket.IO adapter, the feed and trending caches, and anansi's payout lock. Losing that pod loses queued jobs and drops realtime fan-out until it restarts.

For a queue and a cache this is a reasonable cost trade. Before it becomes load-bearing for anything that cannot be reconstructed, move it to DigitalOcean Managed Redis (or at minimum give it a persistent volume and a clear restart runbook).

Domains

HostRoutes to
playpalz.gg, www.playpalz.ggplaypalz-web:3000
api.playpalz.ggplaypalz-api:4000
socket.playpalz.ggplaypalz-esu:4010 (separate ingress)
grafana.playpalz.ggGrafana
media.playpalz.ggSpaces CDN

DNS records are managed in infra/terraform/dns.tf, including a CAA record restricting certificate issuance. See DNS & TLS.

Where things are defined

ConcernLocation
Cloud resourcesinfra/terraform/
Workloadsinfra/k8s/*.yaml
Secretsinfra/k8s/sealed-secrets/
Logging stack valuesinfra/helm/loki-stack-values.yaml
LiveKit dropletinfra/ansible/
Image build scriptsscripts/build-*.sh

The deployment model

Rendering diagram…

There is no CI/CD

Images are built and pushed from a developer machine and rollouts are triggered by hand. There is no .github/workflows, no automated tests on push, and no gate between a local build and production.

That means a production deploy depends on one person's laptop, Docker cache, and correct --platform linux/amd64 flag. The build scripts do set that flag, so an Apple Silicon machine produces the right architecture — but nothing enforces that tests passed first. Adding a pipeline is the single highest-leverage infrastructure improvement available.

Details in Deploying.

Security posture

Good:

  • Every service container runs as non-root (uid 1001) with allowPrivilegeEscalation: false and all capabilities dropped
  • Database firewall restricts access to the cluster
  • Services communicate over the VPC private network
  • Secrets are sealed and safe to commit — see Secrets Management
  • TLS is enforced with automatic HTTP→HTTPS redirect
  • Pod anti-affinity spreads replicas across nodes

Needs attention:

  • No CI/CD or deployment gate
  • The Mux webhook does not verify signatures
  • rejectUnauthorized: false on the database TLS connection
  • express-rate-limit is not proxy-aware, so limits are effectively global
  • Single-replica Redis with no persistence guarantees

Cost

Roughly $277/month in fixed costs, plus usage-based charges for Mux, CDN egress, and autoscaling. Breakdown and scaling levers in Cost & Scaling.

Next

Internal documentation — PlayPalz platform