The read-only Bitbucket deploy key is authorized and verified. The on-box source at `~/services/remetrics/app/` is in sync with `origin/master` (commit 7aa117df). `git pull` from the server works without any further setup.
### PREREQ 2: Add a `build:` stanza to the remetrics compose file
The current `~/services/remetrics/docker-compose.yml` has no `build:` section — it only references the existing local image tags (`remetrics-web:staging-baked`). Running `docker compose build` today does nothing.
Before the first rebuild, add a `build:` block to both the `remetrics` and `worker` services in the compose file:
```yaml
remetrics:
build:
context: ./app
dockerfile: Dockerfile.production
image: remetrics-web:production-YYYYMMDD
```
The `IMAGE_TAG` variable approach (using a `.env` in the compose directory) lets you build a dated tag without editing the compose file each time:
```yaml
image: remetrics-web:${IMAGE_TAG:-staging-baked}
```
Then set `IMAGE_TAG` in the environment before running `docker compose build`.
This setup should be tested in a dry run (build only, no `up`) before the next time a real update is needed.
`POSTGRES_PASSWORD`, `SECRET_KEY_BASE`, and `OPENAI_API_KEY` have been 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`.
Note: `strapi-remetrics` already used `env_file: ./strapi/.env` — its pattern was already correct; no change was needed there. The `~/services/remetrics/.env` file now EXISTS on the server.
Once images are in a registry, Watchtower could potentially auto-update them. Decide whether that is desired before setting this up (see [Services Inventory](./services-inventory.md) — Watchtower section).
---
## Rails App Update Workflow
**Trigger**: new commits on the Bitbucket `master` branch of `jared_/remetrics_app`.
### Step 1 — Pull latest source
```bash
ssh jared@15.204.247.153
cd ~/services/remetrics/app
git pull origin master
```
Review the log for database migrations or Gemfile.lock changes:
```bash
git log --oneline origin/master..HEAD # what just pulled
git diff HEAD~1 HEAD -- db/migrate/ # any new migrations?
git diff HEAD~1 HEAD -- Gemfile.lock # gem changes?
```
- **Gem changes** (Gemfile.lock changed): a full image rebuild is required (Step 3).
- **No gem changes, no assets changed**: you may be able to skip the rebuild and just restart (the volume mount overlays source on top of the baked image). Test this carefully — if in doubt, rebuild.
- **Asset changes** (app/assets/, app/javascript/): rebuild required; assets are precompiled at image build time.
### Step 2 — Tag the current image as rollback
Before touching anything, preserve the current image:
```bash
DATE=$(date +%Y%m%d)
docker tag remetrics-web:staging-baked remetrics-web:rollback-$DATE
docker tag remetrics-worker:staging-baked remetrics-worker:rollback-$DATE
```
Keep at most two rollback tags; remove older ones with `docker rmi`.
### Step 3 — Build new images
> **Requires PREREQ 2 (build: stanza) to be complete first.**
Expected build time: 3–8 minutes if Gemfile unchanged (bundle cache hit); longer if gems changed.
Update the compose file (or `.env` IMAGE_TAG variable) to reference the new dated tag before proceeding.
### Step 4 — Run database migrations
Run migrations against the live database using a one-off container before cutting over traffic. This allows aborting if migrations fail without taking the site down:
> **Note**: `~/services/remetrics/.env` contains `POSTGRES_PASSWORD` and the other secrets — use `--env-file` to pass them without hardcoding values in the command. The `DATABASE_URL` is constructed from these vars inside the Rails app.
# Edit compose IMAGE_TAG back to the rollback tag, then:
docker compose up -d --no-deps remetrics worker
```
Rolling back database migrations is generally not safe. If a migration ran and the new code is being reverted, prefer a forward fix over schema rollback.
---
## Code-Only Restart (No Gem/Asset Changes)
When a git pull brings only Ruby code changes (no Gemfile.lock, no asset changes), you may be able to skip the rebuild because the bind mount overlays the source on top of the baked image:
```bash
cd ~/services/remetrics/app && git pull origin master
# Verify: no Gemfile.lock or db/migrate/ changes
cd ~/services/remetrics && docker compose restart remetrics worker
```
This is faster but less reliable — if any initialization depends on the build-time environment (precompiled assets, baked gems), the restart alone may not pick up all changes. When in doubt, do a full rebuild.
---
## Strapi Update Workflow
Strapi updates are riskier than Rails updates because of unclear image provenance.
**Day-to-day content changes** (new articles, config edits): No image update needed. All Strapi content and config live in the bind-mounted `~/services/strapi-remetrics/strapi/` directories. Changes are live immediately.
**Strapi version upgrade** (security patch, feature update):
> The current image (`strapi-remetrics:staging`) originated from a private GCP registry and is 3 years old. The compose `# Do NOT pull / rebuild` note reflects that the GCP registry is gone. To do a version upgrade you must write a Dockerfile.
2.**Write a Dockerfile** in `~/services/strapi-remetrics/` using the on-disk source:
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY strapi/package.json strapi/yarn.lock ./
RUN yarn install --frozen-lockfile
RUN yarn add @strapi-community/strapi-provider-upload-google-cloud-storage
COPY strapi/src/ ./src/
COPY strapi/config/ ./config/
RUN yarn build
CMD ["yarn", "start"]
```
3.**Add a `build:` stanza** to `~/services/strapi-remetrics/docker-compose.yml` and remove the `Do NOT pull / rebuild` comment once the Dockerfile exists.
6. Strapi auto-runs schema migrations on startup — no manual step needed for SQLite.
---
## Asset Precompile Note
`Dockerfile.production` runs `bin/rails assets:precompile` at build time. However, the compose file mounts `./app:/app` into the container, which overlays the bind-mount directory over `/app`. If `~/services/remetrics/app/public/assets/` exists on disk (i.e., is not gitignored and empty), the compiled assets from the image layer are shadowed by the on-disk directory.
**Recommended**: ensure `app/public/assets/` is in `.gitignore` and not present on the host (or is empty) so that the image's precompiled assets are used. Alternatively, remove the `./app:/app` bind mount entirely and COPY source into the image at build time — this makes images fully self-contained and removes the shadowing concern.
---
## Build Location
Build on the OVH server directly (no external CI needed). The server has the source, the Dockerfile, and sufficient resources (~22 GiB RAM, 8 vCores). The house style for all other services uses pre-built upstream images with no CI pipeline; introducing Bitbucket Pipelines or GitHub Actions adds registry costs and complexity not warranted for a single-server setup.
---
## Version Upgrade Priority Order
When a rebuild is scheduled, upgrade these in order (each requires a Dockerfile change):
1.**Node 16 → 20** in `Dockerfile.production` (Node 16 is EOL since Sept 2023)
2.**Strapi 4.9.2 → latest 4.x** (security patches, same major — test on staging)