{"id":51664904,"url":"https://github.com/thunderbird/discourse-deploy","last_synced_at":"2026-07-14T19:34:16.297Z","repository":{"id":370493667,"uuid":"1231249085","full_name":"thunderbird/discourse-deploy","owner":"thunderbird","description":"Deploy manifests for self-hosted Discourse on mzla-eks-workloads01","archived":false,"fork":false,"pushed_at":"2026-05-07T18:16:26.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-09T13:28:33.730Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://discourse.thunderbird.net/","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thunderbird.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T19:26:41.000Z","updated_at":"2026-05-07T18:16:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thunderbird/discourse-deploy","commit_stats":null,"previous_names":["thunderbird/discourse-deploy"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/thunderbird/discourse-deploy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thunderbird%2Fdiscourse-deploy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thunderbird%2Fdiscourse-deploy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thunderbird%2Fdiscourse-deploy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thunderbird%2Fdiscourse-deploy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thunderbird","download_url":"https://codeload.github.com/thunderbird/discourse-deploy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thunderbird%2Fdiscourse-deploy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35476731,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-14T02:00:06.603Z","response_time":114,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-14T19:34:15.218Z","updated_at":"2026-07-14T19:34:16.286Z","avatar_url":"https://github.com/thunderbird.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Discourse — EKS Deployment\n\nThis repo holds the ArgoCD/ACK manifests and container build that run **[Discourse](https://www.discourse.org/)** self-hosted on the **`mzla-eks-workloads01`** EKS cluster (`eu-central-1`, account `668807881758`). The forum is exposed at **\u003chttps://discourse.thunderbird.net\u003e** via Cloudflare Tunnel.\n\nIt is the planned replacement for Topicbox. Data migration from Topicbox is a later phase.\n\n\u003e **Audience:** SREs landing here for the first time should read *Architecture* + *Where things live* to understand the system. Senior SREs should jump to *Operational notes* + *Gotchas* for the things that bit us during initial deployment.\n\n**Source of truth for the deployment plan and decision history:** [thunderbird/platform-infrastructure#279](https://github.com/thunderbird/platform-infrastructure/issues/279) (epic), [#280](https://github.com/thunderbird/platform-infrastructure/pull/280) (initial Pulumi), [#285](https://github.com/thunderbird/platform-infrastructure/pull/285) (ECR + GHA OIDC), [#287](https://github.com/thunderbird/platform-infrastructure/pull/287) (YACE IRSA).\n\n---\n\n## 1. What is Discourse?\n\nDiscourse is a Rails (Ruby 3.4) forum platform. Upstream officially supports Docker via [`discourse_docker`](https://github.com/discourse/discourse_docker) but [does not officially support Kubernetes](https://meta.discourse.org/t/installing-on-kubernetes/49329). The image we ship is built by `discourse_docker`'s `launcher bootstrap` (which compiles assets, installs gems, and bakes Postgres + Redis binaries that go unused at runtime since we point at external services).\n\n### Components we run\n\n| Component | Image | Purpose |\n|-----------|-------|---------|\n| `discourse-web` | `668807881758.dkr.ecr.eu-central-1.amazonaws.com/discourse:v0.1.2` | nginx → Puma + Rails on `:80`. 2 replicas. discourse-prometheus collector on `:9405` (pod-network only). |\n| `discourse-sidekiq` | same image, `bundle exec sidekiq` | Background jobs (mail, search index, digest). Reports metrics to web's collector via IPC. 1 replica. |\n| `discourse-tunnel` | `cloudflare/cloudflared` (managed by `cloudflare-operator`) | Outbound tunnel. Zero inbound ports. |\n| `discourse-postgres` (RDS) | `postgres:16.6` (AWS RDS) | Application database. 7-day automated backup retention. |\n| `discourse-redis` (ElastiCache) | `redis:7.1` (AWS ElastiCache) | Cache + Sidekiq queue. AUTH-token enabled, transit encryption on. |\n| `mzla-discourse-uploads` (S3) | — | Avatars, attachments, post images, backups. Pod access via IRSA (no static keys). |\n| SES (eu-central-1) | — | Outbound SMTP. Production-access enabled (50k/24h, 14/sec). |\n| `yace` | `quay.io/prometheuscommunity/yet-another-cloudwatch-exporter:v0.64.0` | Pulls AWS service metrics for the dashboard. |\n\n### Plugins baked into the image\n\nBundled by `discourse_docker` base — `openid-connect`, `chat`, `solved`, `presence`, `data-explorer`, plus 30+ others (see `Admin → Plugins`).\n\nCloned in by `container/containers/app.yml` (NOT bundled in the base):\n- `discourse-prometheus` — exposes `/metrics` on `:9405` for VMAgent\n\n---\n\n## 2. Architecture\n\n```\n            Internet\n                │\n                ▼\n  ┌───────────────────────────────┐\n  │  Cloudflare edge (managed     │\n  │  challenge + TLS termination  │\n  │  on thunderbird.net zone)     │\n  └───────────────────────────────┘\n                │  outbound tunnel\n                ▼\n  ┌───────────────────────────────┐    namespace: discourse\n  │  discourse-tunnel             │    on mzla-eks-workloads01 (eu-central-1)\n  │  (cloudflared, 4 conns to     │\n  │   fra03/fra14/fra16/fra08)    │\n  └───────────────────────────────┘\n                │  http://discourse-web:80 → nginx → unicorn(127.0.0.1:3000)\n                ▼\n  ┌───────────────────────────────┐\n  │  discourse-web (nginx + Puma) │──── :9405/metrics ──► VMAgent\n  │  - HTTP API + UI              │      (only after PROMETHEUS_WEBSERVER_BIND=0.0.0.0)\n  │  - discourse-prometheus       │\n  │  - aggregates sidekiq metrics │\n  │    via Unix socket IPC        │\n  └───────────────────────────────┘\n            │            │\n            │            └──► s3:mzla-discourse-uploads (IRSA: workloads-prod-discourse-default)\n            │\n   ┌────────┴────────┐\n   ▼                 ▼\n┌──────────────┐    ┌──────────────────┐\n│ ElastiCache  │    │  RDS Postgres    │\n│ Redis 7.1    │    │  discourse-      │\n│ (AUTH+TLS)   │    │  postgres        │\n│ master.disc..│    │  16.6, db.t4g.   │\n└──────────────┘    │  medium, 30Gi    │\n        ▲           │  gp3, encrypted  │\n        │ jobs      │  7d backups      │\n        │           └──────────────────┘\n   ┌────┴────────────────┐    ▲\n   │ discourse-sidekiq   │────┘\n   │ (bundle exec sidekiq│   reports metrics → web's collector via Unix socket\n   └─────────────────────┘\n```\n\nYACE runs in the same namespace, scrapes CloudWatch via IRSA, exposes Prometheus metrics on `:5000` for VMAgent.\n\n---\n\n## 3. Where things live\n\n### In this repo\n\n```\ndiscourse-deploy/\n├── README.md                                       you are here\n├── .github/\n│   ├── CODEOWNERS                                  @thunderbird/platform-infrastructure\n│   └── workflows/build.yml                         build + push to ECR on tag\n├── container/\n│   ├── Dockerfile                                  unused (placeholder; we use launcher bootstrap)\n│   └── containers/app.yml                          discourse_docker config (templates, plugin clones)\n├── argocd/\n│   ├── aws-resources/\n│   │   ├── rds-postgres.yaml                       ACK DBSubnetGroup + DBInstance (postgres 16.6)\n│   │   ├── elasticache-redis.yaml                  ACK CacheSubnetGroup + ReplicationGroup (redis 7.1)\n│   │   └── s3-bucket.yaml                          ACK Bucket: mzla-discourse-uploads\n│   ├── secrets/\n│   │   ├── discourse-db-credentials.yaml           ES → mzla/discourse/db\n│   │   ├── discourse-redis-credentials.yaml        ES → mzla/discourse/redis\n│   │   ├── discourse-app-secrets.yaml              ES → mzla/discourse/app\n│   │   ├── discourse-smtp-credentials.yaml         ES → mzla/discourse/smtp\n│   │   └── cloudflare-credentials.yaml             ES → mzla/twenty/cloudflare (shared)\n│   ├── workloads/\n│   │   ├── default-serviceaccount.yaml             IRSA annotation overlay (S3 access)\n│   │   ├── rds-bootstrap-job.yaml                  CREATE USER + GRANTs + hstore + pg_trgm + vector\n│   │   ├── discourse-config.yaml                   ConfigMap of DISCOURSE_* env\n│   │   ├── discourse-web.yaml                      Deployment + Service for nginx+Puma (image pinned)\n│   │   ├── discourse-sidekiq.yaml                  Deployment for Sidekiq workers (image pinned)\n│   │   ├── discourse-migrate-job.yaml              Sync-hook Job: bundle exec rake db:migrate\n│   │   └── cloudflare-tunnel.yaml                  Tunnel + TunnelBinding (discourse.thunderbird.net)\n│   └── observability/\n│       ├── discourse-vmpodscrape.yaml              VMPodScrape on web pods only (port 9405)\n│       └── yace.yaml                               YACE Deployment + ConfigMap + scrape (port 5000)\n```\n\n### In `thunderbird/platform-infrastructure`\n\n- `argocd/projects/discourse.yaml` — AppProject scoping discourse to workloads01\n- `argocd/workloads/apps/discourse-app-of-apps.yaml` — sync pointer at this repo's `argocd/`\n- `pulumi/environments/mzla-workloads/config.prod.yaml` — defines IRSA roles `workloads-prod-discourse-default` (S3), `workloads-prod-yace-discourse` (CloudWatch), `workloads-prod-discourse-deploy` (GHA OIDC for ECR push), the `workloads-prod-discourse-ses` IAM user (SES SMTP), and the four AWS Secrets Manager secrets at `mzla/discourse/{db,redis,app,smtp}`. Plus the ECR repo `discourse` and GitHub OIDC provider.\n\n### In `thunderbird/discourse-theme-bolt`\n\nThe Bolt-styled Discourse theme. Public; v0.1.0 covers basic palette + Inter font. Installed via Admin → Customize → Themes → Install from a git repository → tag `v0.1.0`.\n\n### In `thunderbird/platform-grafana`\n\n`terraform/dashboards/discourse/overview.json` — Grafana dashboard at \u003chttps://grafana.pi.thunderbird.net/d/discourse-overview\u003e. Datasource UID `P4169E866C3094E38` (the workloads01 VictoriaMetrics).\n\n---\n\n## 4. Bring-up runbook (for a fresh cluster or DR)\n\nOrder matters because the ACK CRDs report endpoints in their `status` only **after** AWS reconciles them; Discourse needs those endpoints in `discourse-config` and `rds-bootstrap-job` to come up.\n\n1. **Pulumi up** in `mzla-workloads` to create IRSA, SES IAM user, and the four SM secrets.\n2. **Provision ECR repo + GHA OIDC role** (also via Pulumi). Create the `production` GitHub Environment in this repo: `gh api -X PUT repos/thunderbird/discourse-deploy/environments/production`.\n3. **Build the container image** by tagging this repo `v0.1.x`. The GHA workflow runs `launcher bootstrap` on `ubuntu-24.04-arm` (so the resulting image is arm64 — workloads01's default node groups are Graviton). Image is pushed to ECR as `:v0.1.x` and `:latest`.\n4. **Register the app** by merging `argocd/projects/discourse.yaml` and `argocd/workloads/apps/discourse-app-of-apps.yaml` in `platform-infrastructure`. ArgoCD picks up this repo and starts syncing.\n5. **Patch `\u003cTBD\u003e` placeholders** once RDS + ElastiCache are `available` (~10–15 min for RDS, ~5 min for Redis):\n   ```\n   kubectl get dbinstance discourse-postgres -n discourse \\\n     -o jsonpath='{.status.endpoint.address}'\n   kubectl get replicationgroup discourse-redis -n discourse \\\n     -o jsonpath='{.status.nodeGroups[0].primaryEndpoint.address}'\n   ```\n   PR the resolved values into `discourse-config.yaml` (`DISCOURSE_DB_HOST`, `DISCOURSE_REDIS_HOST`) and `rds-bootstrap-job.yaml` (`PGHOST`).\n6. **Wave 4 hooks** (`rds-bootstrap-job` + `discourse-migrate-job`) run after the patch syncs. Bootstrap creates `discourse_app_user` + grants + extensions (`hstore`, `pg_trgm`, `vector`); migrate runs all Discourse Rails migrations against RDS.\n7. **Wave 5** brings up `discourse-web`, `discourse-sidekiq`, the Cloudflare tunnel, and the VMPodScrape. First Discourse boot takes ~3–5 min.\n8. **Smoke**: `curl https://discourse.thunderbird.net/` (Cloudflare may challenge — open in browser); admin login uses creds from `mzla/discourse/app`.\n\n   ```\n   aws secretsmanager get-secret-value --secret-id mzla/discourse/app \\\n     --profile mzla-workloads --region eu-central-1 --query SecretString --output text | jq .\n   ```\n9. **Enable discourse-prometheus**: Admin → Plugins → discourse-prometheus → toggle on. Pod `:9405` starts serving metrics; VMAgent scrapes within 30s.\n10. **Install the Bolt theme**: Admin → Customize → Themes → Install from a git repository → `https://github.com/thunderbird/discourse-theme-bolt`, tag `v0.1.0`. Set as default.\n\n---\n\n## 5. Operational notes\n\n### Image upgrades\n\nThe `image:` field in `argocd/workloads/discourse-{web,sidekiq,migrate-job}.yaml` is pinned to a specific `v0.1.x` tag (not `:latest`) so a tag-triggered build doesn't auto-replace running pods on restart. To roll a new image:\n\n1. Tag this repo `v0.1.x` — GHA builds and pushes\n2. PR a manifest bump (`v0.1.{x-1}` → `v0.1.x`) in all three places\n3. ArgoCD rolls\n\n### Adding admins\n\nTwo paths:\n- **Promote via UI**: user signs up at `/signup` → admin goes to `/admin/users` → search → \"Grant Admin\"\n- **Auto-promote**: add their email to the `developerEmails` field in `mzla/discourse/app` (comma-separated). Restart web pods to pick up the new env: `kubectl rollout restart deploy/discourse-web -n discourse`\n\n### Restarting pods\n\n```\nkubectl --context arn:aws:eks:eu-central-1:668807881758:cluster/mzla-eks-workloads01 \\\n  rollout restart deploy/discourse-web deploy/discourse-sidekiq -n discourse\n```\n\n### Backups\n\n- RDS: 7-day automated retention, daily snapshot at 03:00 UTC. To take a manual snapshot:\n  ```\n  aws rds create-db-snapshot --db-instance-identifier discourse-postgres \\\n    --db-snapshot-identifier discourse-manual-$(date +%Y%m%d) \\\n    --profile mzla-workloads --region eu-central-1\n  ```\n- S3 uploads: bucket has versioning enabled; restore via `aws s3api list-object-versions` + `restore-object`\n- Discourse-side: Admin → Backups → \"Backup\" creates a tarball in the S3 uploads bucket under `/backups/`\n\n### Logs\n\n- **CloudWatch**: log group `/eks/mzla/mzla-eks-workloads01/applications`, stream pattern `discourse/\u003cpod\u003e/\u003ccontainer\u003e` (3-day retention). Vector ships there in addition to VictoriaLogs.\n- **VictoriaLogs**: at https://grafana.pi.thunderbird.net (LogsQL datasource), filter `_stream:{namespace=\"discourse\"}`\n\n### Metrics + dashboards\n\n- Grafana: \u003chttps://grafana.pi.thunderbird.net/d/discourse-overview\u003e\n- Discourse app metrics from discourse-prometheus on web pods `:9405`\n- AWS metrics from YACE pod `:5000` (filtered by `project=discourse` tag)\n\n---\n\n## 6. Theming\n\nThe Bolt-styled theme lives in [thunderbird/discourse-theme-bolt](https://github.com/thunderbird/discourse-theme-bolt) (public; v0.1.0). Install via Admin UI → Customize → Themes → \"Install from a git repository\". Theme covers basic palette + Inter font; full Bolt palette + dark scheme tracked in that repo's TODO.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthunderbird%2Fdiscourse-deploy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthunderbird%2Fdiscourse-deploy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthunderbird%2Fdiscourse-deploy/lists"}