Skip to content

Infrastructure Overview

How the tomoda app fits into production. This section is app-perspective — what runs where, how a request reaches the backend, how secrets get into a Pod. For the infrastructure itself (Terraform, K8s manifests, Argo CD, cluster operations), see the separate devops docs.

Environments

Three environments. Each is intentionally a strict superset of the previous one so surprises in production are minimized.

Local

Everything runs on the developer's machine. Infra dependencies (Postgres, Redis, MinIO, Photon) come up via docker-compose.dev.yml; the Go backend and the Expo frontend run outside Docker for fast iteration and hot reload.

Component How it runs locally
Postgres + PostGIS kartoza/postgis:15-3.3 container on :5432
Redis redis:7-alpine container on :6379
Object storage MinIO container on :9000 / :9001 (S3-compatible)
Geocoder Photon container on :2322
Backend (Go) go run / Air hot reload, served on :8080
Frontend (Expo) task dev:frontend, Metro on :8081

task dev orchestrates this — see Docker Compose and Getting Started.

Dev (GKE)

A non-prod Kubernetes namespace (tomoda) on the shared GKE cluster used for integration testing and pre-prod validation. Application manifests live in the separate devops/ repo at k8s/apps/tomoda/overlays/dev/. Argo CD watches that repo and reconciles cluster state. Secrets are sourced from GCP Secret Manager via External Secrets Operator.

Prod (GKE)

Same Kubernetes cluster as dev, isolated in the prod namespace. Argo CD reconciles k8s/apps/tomoda/overlays/prod/.

How prod actually runs

Prod is not managed databases. Postgres runs in-cluster as a CloudNative-PG (CNPG) cluster, Redis runs in-cluster as the Bitnami Helm chart. Static assets (avatars, chat images) go to AWS S3 + CloudFront, not GCS. GCS is only used for CNPG backups and Photon indexes. For the full story, see DevOps → Architecture.

Production topology (app-perspective)

User
iOS / Android / Web
Cloudflare DNS
proxied: false
↙   ↘
AWS · ap-northeast-1
CloudFront
assets.tomoda.life
S3 · tomoda-assets-prod
GCP Load Balancer
api / app / www
GKE · gke-tomoda
Traefik Ingress
↙   ↘
Backend Pod
Go · :8080
Frontend Pod
Nginx · :8081
CNPG Postgres
+ PostGIS
Bitnami Redis
Photon
:2322 ClusterIP
Outbound · third-party
Stripe
Resend
Google APIs
OAuth · Places
Backend writes assets to S3 via presigned PUT; CNPG ships WAL+base backups to a GCS backups bucket; Photon pulls its index from a GCS photon-index-usc1 bucket.

Details worth calling out:

  • Two edges, not one. Static assets (assets.tomoda.life) go through AWS CloudFront in ap-northeast-1. API + web traffic (api.tomoda.life, app.tomoda.life, www.tomoda.life) goes through Cloudflare DNS → GCP Load Balancer → Traefik (in-cluster ingress). Cloudflare is DNS-only (proxied: false) — not an edge cache.
  • One ingress, two backends. Traefik routes /api/v1/* and /ws/* to the Go backend Pod, everything else to the frontend Pod (Nginx serving the Expo web static export). See k8s/apps/tomoda/base/ingress.yaml in the devops repo.
  • Frontend is static. The frontend/ container is a multi-stage build (Node 20 → Nginx Alpine). EXPO_PUBLIC_* env vars bake in at build time — see Cloud Build.
  • WebSocket Hub is single-process. The backend's WS hub holds state in memory; the Deployment runs with replicas: 1 for that reason. Scaling horizontally would require migrating presence/fanout to Redis pub/sub. See backend/internal/websocket/hub.go.
  • Photon is internal-only. The Photon Service is ClusterIP at http://photon.platform.svc.cluster.local:2322. No external ingress.
  • Outbound for third-party APIs. Stripe, Resend, Google OAuth, Google Places — all called from the backend. Webhooks come back inbound through Cloudflare → Traefik → backend.

How the app gets there

Concern App side Infra side (devops repo)
Image build cloudbuild-backend.yaml, cloudbuild-frontend.yaml Cloud Build, Artifact Registry
Deployment n/a — GitOps via Argo CD Argo CD, Tomoda K8s app, Deploy runbook
Postgres DSN env var (DB_HOST, DB_USER, DB_PASSWORD) CNPG cluster, Backups
Redis URL env var (REDIS_*) Bitnami Redis
Object storage S3 SDK + IAM creds via s3-uploader-secret S3, CloudFront, Uploader IAM
Photon PHOTON_URL env var → in-cluster service DNS Photon Deployment, Photon indexer
Secrets envFrom: backend-secrets and s3-uploader-secret Secrets Management, External Secrets
TLS n/a — ingress-level cert-manager, TLS
DNS n/a Cloudflare, External-DNS

Region & project

  • GCP project: development-485000 (single project, dev+prod via K8s namespaces)
  • GKE cluster: gke-tomoda in asia-east1-a
  • Artifact Registry: asia-east1-docker.pkg.dev/development-485000/tomoda-{dev,prod}-repo/
  • AWS region: ap-northeast-1 (static assets only)

What's not in this section