The Rails web and worker images (`staging-baked`) were built during the 2026-06-24 migration via a `docker commit`-style bake. The primary copy is on this server; a DR backup was pushed to GHCR on 2026-06-25:
-`ghcr.io/jaredswanson/remetrics-worker:staging-baked` / `:dr-2026-06-25` (same image as web — one build, two commands)
GHCR is private; pulling requires `docker login ghcr.io` with a token that has `read:packages`. See [remetrics Update Workflow](./remetrics-update-workflow.md) for how to rebuild them.
The Strapi image (`strapi-remetrics:staging`) originated from a private GCP registry but is now a local tag, also backed up to GHCR (`ghcr.io/jaredswanson/strapi-remetrics:staging` / `:dr-2026-06-25`). The compose file carries a `# Do NOT pull / rebuild` comment because that GCP registry is gone. See the Strapi section of the update workflow before attempting any Strapi image changes.
---
## Domains and TLS
| Domain | Service | Cert Resolver |
|---|---|---|
| `remetrics.io` | Rails web | `cloudflare` (DNS-01) |
| `www.remetrics.io` | Rails web | `cloudflare` (DNS-01) |
All three domains use the `cloudflare` ACME resolver (DNS-01), not the default `letsencrypt` HTTP-01 resolver used by other services. DNS-01 was required to pre-issue certificates before the DNS cutover window. See [Cert Management](./cert-management.md) for where certs are stored and how to check expiry.
---
## CMS URL — Hardcoded Coupling
**Important**: The Rails app fetches CMS content from a **hardcoded** URL:
```
https://cms.remetrics.io
```
This is not env-configurable. It is set directly in the app source at:
-`app/models/strapi/article.rb`
-`app/models/strapi/help_page.rb`
**Consequence**: If `strapi-remetrics` is down or `cms.remetrics.io` is unreachable, the Rails web process will fail to render any CMS-backed pages. Strapi and Rails must be kept running together. This also means that renaming the CMS domain or moving Strapi to a new host requires a code change and image rebuild — not just a config change.
---
## Image Provenance
Source repository for the Rails app: `git@bitbucket.org:jared_/remetrics_app.git`
The on-disk source is at `~/services/remetrics/app/` (bind-mounted into both web and worker containers at `/app`). This is a live git repo tracking the Bitbucket remote. The Bitbucket read-only deploy key is configured and verified; on-box source is in sync with `origin/master` (commit 7aa117df as of 2026-06-25).
The Strapi source (package.json, src/, config/) lives at `~/services/strapi-remetrics/strapi/`. There is no separate Bitbucket repo for Strapi; the source files are the canonical copy on this server.
---
## Data Layout
### Rails database
- Container: `remetrics-db-1` (postgres:15)
- Database name: `remetrics_production`
- User: `estatioro`
- Storage: Docker named volume `remetrics_remetrics_pgdata`
- 480 users migrated from DigitalOcean (zero data loss, 2026-06-24)
The named volume is backed up nightly by `~/services/backup.sh` via `pg_dumpall`. Confirm with:
- Backed up nightly as part of the strapi-remetrics service-files tar
### Strapi media uploads
- Storage: GCS bucket `remetrics-strapi`
- GCS service account key: `~/services/strapi-remetrics/strapi/gcs/` (bind-mounted)
- Not backed up by backup.sh (lives in GCS, not on-server)
### Redis
Redis (`redis:alpine`) has **no named volume** — data is ephemeral and will be lost on container removal. This is acceptable because Redis is used only for the Sidekiq job queue; job state is persisted in Postgres. Active in-flight jobs may be lost if Redis is removed, but queued jobs that have already been enqueued will requeue on next start.
### Migration artifact
`~/services/remetrics/remetrics_production.dump` is the Postgres dump from the 2026-06-24 migration. It is safe to move to backup storage and remove from the services directory once you have confirmed nightly backups are running cleanly.
---
## Sidekiq Health
Sidekiq runs as the `worker` service (`remetrics-worker-1`). There is no healthcheck configured on this container.
| Strapi | 4.9.2 | ~16 minor releases behind latest 4.x; Strapi 5 is a breaking major |
| PostgreSQL | 15 | Supported until November 2027 — no urgency |
See [remetrics Update Workflow](./remetrics-update-workflow.md) for the recommended upgrade sequence when doing a rebuild.
---
## Divergences from House Style
These are known differences between remetrics and how other services are configured. They are documented here so future changes don't inadvertently "fix" them without understanding the intent:
1.**Cert resolver**: remetrics uses `cloudflare` (DNS-01) while all other services use `letsencrypt` (HTTP-01). This is intentional — DNS-01 was required for zero-downtime cutover cert pre-issue.
2.**No HTTP→HTTPS redirect router**: remetrics has no explicit port-80 router with redirect middleware. Other services (e.g., BestSolarTech) define explicit HTTP routers. Whether Traefik's global `entrypoints.web.http.redirections` covers port-80 for remetrics.io is unverified — **open question**.
3.**Restart policy**: `unless-stopped` (not `always`). Manual `docker stop` will not auto-restart the containers after a daemon restart until you run `docker compose up -d` again.
4.**Secrets in compose** — RESOLVED (2026-06-25): `POSTGRES_PASSWORD`, `SECRET_KEY_BASE`, and `OPENAI_API_KEY` were extracted from inline cleartext in `~/services/remetrics/docker-compose.yml` to a gitignored `~/services/remetrics/.env` (mode 600), referenced via `${VAR}` interpolation. Verified byte-identical resolved config; zero container disruption. Backup: `docker-compose.yml.bak-2026-06-25`. This matches house style. Note: `strapi-remetrics` already used `env_file: ./strapi/.env` — no change was needed there.
- **TODO (follow-up)**: The GCS service account JSON at `~/services/strapi-remetrics/strapi/gcs/gcs-service-account.json` should be rotated and stored in Vaultwarden as a file credential. This key predates the OVH migration and has not been rotated.
5.**No healthchecks**: None of the remetrics containers define Docker healthchecks. `depends_on` runs without condition checks — the db may not be ready when Rails starts. In practice puma retries on boot, but this can cause slower start times.
6.**Shared `internal` network**: The `internal` network (172.21.0.0/16) is shared server-wide. Any future service added to `internal` can reach remetrics Postgres (5432) and Redis (6379) by container DNS name. Redis has no password.
---
## Rollback to DigitalOcean
The DigitalOcean remetrics web droplet at **147.182.131.217** is currently **frozen** (apex returns 503, Sidekiq stopped) and kept as a short-term rollback. It has NOT been deleted.
To roll back to DigitalOcean:
1.**Stop new writes on OVH** — put the Rails app in maintenance mode or stop the web container:
3.**Repoint DNS** — change the three Cloudflare A-records (`remetrics.io`, `www.remetrics.io`, `cms.remetrics.io`) from `15.204.247.153` back to `147.182.131.217`.
4.**Unfreeze the DO droplet** — restore the Sidekiq worker and any nginx config you stopped during the cutover freeze.
5.**Restore OVH data to DO** — apply the step-2 dump to the DO Postgres if any new data needs to be preserved.
The DigitalOcean droplet still holds its original data as of the 2026-06-24 cutover. After rollback, reassess whether to re-migrate or decommission the OVH containers.
**Decommission decision**: the DO droplet should be deleted once remetrics has been stable on OVH for a sufficient burn-in period (suggest 2–4 weeks). Update this doc and `migrations/migration-plan.md` when decommission is complete.