From 93b127f112c8c0ed44991b14a305a91dd41df14d Mon Sep 17 00:00:00 2001 From: jknapp Date: Mon, 18 May 2026 09:34:23 -0700 Subject: [PATCH] feat(compose): opt-in external-Postgres override Adds `docker-compose.external-db.yml` so teams can point the stack at a managed Postgres (RDS, Cloud SQL, etc.) without forking the base compose file. Disables the bundled `db` service via an unreachable `profiles` label and replaces `depends_on` / `DATABASE_URL` on `migrator` and `app` with `!override`-tagged blocks that read `DATABASE_URL` straight from `.env`. Default `docker compose up -d` flow is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 4 ++ README.md | 71 ++++++++++++++++++++++++++++++++++ docker-compose.external-db.yml | 54 ++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 docker-compose.external-db.yml diff --git a/.env.example b/.env.example index cd4df4a..abd8a1f 100644 --- a/.env.example +++ b/.env.example @@ -47,6 +47,10 @@ POSTGRES_DB=memory # if you point at an external Postgres. # DATABASE_URL=postgres://memory:...@db:5432/memory +# When using docker-compose.external-db.yml, set DATABASE_URL explicitly. +# Example for AWS RDS Postgres with SSL: +# DATABASE_URL=postgres://memory:STRONG_PASSWORD@your-rds.region.rds.amazonaws.com:5432/memory?sslmode=require + # ----------------------------------------------------------------------------- # Embedder sidecar. Default points at the in-compose service. # ----------------------------------------------------------------------------- diff --git a/README.md b/README.md index 76f60aa..0bd5e08 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,77 @@ Mode column: **A** = external proxy (default), **B** = built-in Caddy TLS. --- +## External Postgres (RDS, Cloud SQL, etc.) + +By default the compose stack runs a bundled `pgvector/pgvector:pg16` container +with its data on a Docker volume. For production deployments you may prefer +a managed Postgres (AWS RDS, GCP Cloud SQL, Azure Database for PostgreSQL, +…). An opt-in override file disables the bundled `db` service and lets the +app point at any reachable Postgres. + +### When to use + +- You already have a managed Postgres you trust (point-in-time recovery, + automated snapshots, monitoring, IAM, etc.). +- You want to scale the database independently of the app host. +- Compliance / data-residency rules require the DB to live elsewhere. + +If none of that applies, the bundled `db` is fine — keep using +`docker compose up -d` and skip this section. + +### Connection requirements + +- The DB must be reachable from wherever the app runs (security group / + firewall / VPC peering / private link as appropriate). +- SSL is strongly recommended. For RDS append `?sslmode=require` to the URL. +- The DB user needs enough privileges on first boot to install extensions + (`pgvector`, `pg_trgm`, `pgcrypto`). The migrator runs + `CREATE EXTENSION IF NOT EXISTS` for each — on RDS the user needs the + `rds_superuser` role, or have an admin pre-create the extensions and + grant the app's user `USAGE` on them. + +### Extension requirements + +- **pgvector** — vector search. RDS Postgres ≥ 15.5 ships pgvector as a + trusted extension; 16.x (what this project targets) supports it out of + the box. Cloud SQL and Azure Database for PostgreSQL also expose it as + a flagged / configurable extension. +- **pg_trgm** — trigram index for hybrid lexical search. +- **pgcrypto** — `gen_random_uuid()` for ID generation. + +### Compose invocation + +```bash +docker compose -f docker-compose.yml -f docker-compose.external-db.yml up -d +``` + +Set `DATABASE_URL` in `.env` to your managed-DB connection string before +running this — the `POSTGRES_*` variables are no longer consulted in this +mode. See `.env.example` for the RDS-style example URL. + +Combine with the built-in TLS profile if you want Caddy as well: + +```bash +docker compose -f docker-compose.yml -f docker-compose.external-db.yml --profile tls up -d +``` + +### What about backups? + +You give up the `db_data` volume (which you'd back up with whatever volume +backup story you already use) and inherit your managed provider's backup +story instead — RDS automated snapshots + point-in-time recovery, Cloud SQL +automated backups, Azure server-level backups, etc. In practice this is the +main reason to switch: pushing backup-and-restore to a managed service that +already does it well. + +### AWS Fargate / managed deploy + +For a fully-managed deployment (Fargate app + RDS DB, no Docker host of +your own), see [`terraform/README.md`](terraform/README.md) for an +opinionated Terraform module that wires it all up. + +--- + ## OIDC provider setup You need **two** OAuth2 / OIDC clients on your identity provider: diff --git a/docker-compose.external-db.yml b/docker-compose.external-db.yml new file mode 100644 index 0000000..bc107af --- /dev/null +++ b/docker-compose.external-db.yml @@ -0,0 +1,54 @@ +# ============================================================================= +# shared-memory — external Postgres override. +# +# Use this override when you want to point the app at a managed Postgres +# (AWS RDS, GCP Cloud SQL, Azure Database for PostgreSQL, your own VM, ...) +# instead of the bundled `db` container. +# +# Invocation (always together with the base file): +# +# docker compose -f docker-compose.yml -f docker-compose.external-db.yml up -d +# +# The caller MUST set `DATABASE_URL` explicitly in `.env` so that `migrator` +# and `app` know where to connect. The `POSTGRES_*` variables are not used +# in this mode (the bundled `db` service is disabled below). Example: +# +# DATABASE_URL=postgres://memory:STRONG_PASSWORD@your-rds.region.rds.amazonaws.com:5432/memory?sslmode=require +# +# The DB user needs privileges to `CREATE EXTENSION` for pgvector, pg_trgm, +# and pgcrypto on first run — on RDS that means the `rds_superuser` role, or +# pre-create the extensions yourself. See README "External Postgres". +# ============================================================================= + +services: + db: + # Park the bundled DB on a profile that nothing ever enables. Compose + # only starts services whose profile list is empty OR matches a + # `--profile` flag on the command line. "never" is not a magic name — + # it's just a label we promise not to pass, so the service stays down. + profiles: ["never"] + + migrator: + # Docker compose merges `depends_on` by key — listing `embedder` here + # alone would keep the base file's `db` entry and break with + # "depends on undefined service db". The `!override` tag (compose 2.24+) + # replaces the whole block instead of merging. + depends_on: !override + embedder: + condition: service_healthy + environment: + # The base file hardcodes DATABASE_URL to point at the bundled `db` + # service. Override it to pass through whatever the operator set in + # `.env` (e.g. an RDS endpoint with sslmode=require). + DATABASE_URL: ${DATABASE_URL:?DATABASE_URL not set in .env (required with external-db override)} + + app: + # Same merge caveat as above — fully replace the block, keep embedder + # and migrator deps. + depends_on: !override + embedder: + condition: service_healthy + migrator: + condition: service_completed_successfully + environment: + DATABASE_URL: ${DATABASE_URL:?DATABASE_URL not set in .env (required with external-db override)}