Skip to content

VPS dev-tools: Dev group (Forgejo, Mattermost, Plane) on Lightsail#1

Merged
govtech42 merged 15 commits into
mainfrom
vps-dev-tools-opentofu
Jun 25, 2026
Merged

VPS dev-tools: Dev group (Forgejo, Mattermost, Plane) on Lightsail#1
govtech42 merged 15 commits into
mainfrom
vps-dev-tools-opentofu

Conversation

@govtech42

Copy link
Copy Markdown
Owner

Multi-host dev-tools platform. This PR delivers the Dev group end-to-end,
locally verified via Colima (17/17 smoke green from a clean bring-up).

What's here

  • infra/ — Lightsail provisioning (AWS CLI, no OpenTofu): create/destroy
    scripts, user-data (Docker, 4GB swap, /data block disk), firewall.
  • apps/ — shared per-app contexts: caddy, postgres (postgres_fdw),
    forgejo, mattermost, postgrest, adminer, plane (fork scaffold).
  • deploy/dev/ — compose for the Dev host; DATA_ROOT for dev/prod parity;
    Plane services behind a plane profile (off locally).
  • Reporting/BI — shared Postgres with per-app DBs; reporting DB pulls app
    tables via FDW into curated read-only views; PostgREST serves them; N2 (no
    public DB/PostgREST/Adminer ports).
  • Tests — Makefile + test/ (lint + live smoke).
  • Docs — design spec, implementation plan, RUNBOOK; CLAUDE.md doctrine.

Verified locally (Colima)

Forgejo (healthz, 121 tables), Mattermost (ping OK, migrated), shared Postgres
(4 DBs + roles + postgres_fdw), reporting views readable via FDW, PostgREST
serving a view, N2 (no host ports).

Deferred to deploy (needs owner input)

  • Real deploy/dev/.env secrets + DATA_ROOT=/data.
  • code42/plane fork + GHCR image build (Plane profile).
  • DNS A-records + live TLS verification on the VPS.

Roadmap (next branch)

Support group (Planka + Chatwoot, 8GB host); Monitoring group (Beszel) on radar.

🤖 Generated with Claude Code

govtech42 and others added 15 commits June 25, 2026 15:15
Single EC2 host (OpenTofu) running Forgejo, Mattermost, and Plane in Docker
with a shared Postgres. Plane built from a fork; Forgejo/Mattermost overlay
official images. Curated reporting DB via postgres_fdw exposed through PostgREST
for BI. N2 network model (DB/PostgREST closed, SSH tunnel only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop OpenTofu: provision via AWS CLI script in infra/scripts/
- Lightsail 8GB plan, static IP, instance firewall, separate block disk at /data
- Plane registry ECR -> GHCR (Lightsail has no IAM instance role)
- All provisioning under infra/; runtime stack under apps/ with one
  self-contained context per application
- Characterize the Plane fork: submodule at apps/plane/upstream, code42 branch,
  CHANGES.md divergence log, build off-host -> GHCR -> host pulls

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…se 2

Grounded Plane's real self-host topology (admin, live, RabbitMQ added upstream;
~11 containers). 3 core apps fit comfortably in 16GB with headroom reserved for
the phase-2 support apps. plane-db dropped for shared Postgres; Adminer replaces
Supabase Studio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Repo is now group-aware: apps/ holds shared per-app contexts; deploy/<group>/
  composes them into one host each (dev=16GB, support=8GB, monitoring radar)
- Support apps (Planka, Chatwoot) move to a SECOND Lightsail 8GB host, not the
  Dev box; record cross-host BI as an open decision (default: per-group reporting
  to preserve N2)
- Monitoring group on radar, starting with Beszel (to confirm)
- Repoint Dev plan compose to deploy/dev/, build contexts to ../../apps/<app>,
  secrets to deploy/dev/.env
- Harden .gitignore for .env.plane and Lightsail .pem keys

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- deploy/dev/docker-compose.yml with DATA_ROOT for dev/prod parity
- block-style volumes (flow mapping breaks on ${} brace)
- Makefile (colima-up, datadirs, lint, up, smoke, test) + test/ suite
- local .env (gitignored) for Colima testing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- postgres:16 (bundled postgres_fdw); init creates forgejo/mattermost/plane/
  reporting DBs, app owners, fdw_reader with default-privilege grants on future
  app tables, bi_reader + authenticator for PostgREST
- fix: psql does not substitute :'var' inside DO $$ blocks -> create roles via
  \gexec, set authenticator password via plain ALTER ROLE
- smoke: 13 checks green (DBs, roles, postgres_fdw, N2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FROM forgejo:11 overlay; DB via FORGEJO__database__* env; INSTALL_LOCK=true so
migrations run on first boot (121 tables, repository present). healthz pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FROM mattermost-team-edition:10.11 (amd64-only); platform pinned, auto-migrates
  (73+ tables, channels present), ping OK
- Makefile: cross-build mattermost with buildx --load on arm64 dev box; native
  build elsewhere. up no longer force-rebuilds
- smoke: HTTP checks now run from a curl sidecar on the compose network (app
  images lack curl/wget)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iner

- explicit foreign tables (minimal columns, enum->text) instead of IMPORT FOREIGN
  SCHEMA, which broke on Mattermost's channel_type enum
- reporting.repositories (forgejo) + reporting.chat_channels (mattermost) views,
  bi_reader SELECT-only; Plane view deferred until its stack is live
- PostgREST over reporting (authenticator->bi_reader) + Adminer, both N2 (no host
  port); make reporting target applies the layer post-migration
- smoke: 17 green incl. FDW read + PostgREST serving a view + N2 no-ports

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- apps/plane: README (fork branch model, off-host GHCR build, rebase loop),
  CHANGES.md, .env.plane.example reference, reporting-plane.sql (deferred FT+view)
- compose: 11 Plane services under profile 'plane' (off locally/by default; on via
  --profile plane on the VPS). DRY backend via x-plane-backend anchor; points at
  shared Postgres 'plane', own Valkey/RabbitMQ/MinIO
- both default and plane profiles lint clean; local core stack unaffected

Fork build deferred to deploy (code42/plane fork + GHCR images not built yet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- create-lightsail.sh: instance (xlarge_2_0/16GB default; large_2_0 for support),
  static IP, attached block disk -> /data, firewall (22 owner-IP, 80, 443)
- user-data.sh: docker, 4GB swap, mount /data + bind dirs, clone repo
- destroy-lightsail.sh: guarded; keeps the data disk
- firewall.json + infra/README runbook; all scripts pass bash -n, json valid

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…heck

- full 'make up' from wiped .data -> 17/17 smoke (Forgejo, Mattermost, shared
  Postgres, FDW reporting views, PostgREST, N2); Plane skipped (profile off)
- smoke: retry mattermost ping (web server lags migration under emulation)
- docs/RUNBOOK.md: local (Colima) + VPS deploy + BI tunnel + plane fork + dangers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant