Upgrading an existing host
Ce contenu n’est pas encore disponible dans votre langue.
A host deployed from master before these changes runs seven services and one /opt/pia/.env.prod. The current stack runs twelve, across two environment files, and adds a Temporal backend for the operator runtime. Everything that needs doing is on the host — the images take care of themselves.
Run docker compose -f docker-compose.prod.yml ps first. If there is no pia-migrate in the output, this page applies to you.
What changes
Section titled “What changes”| Before | After | |
|---|---|---|
| Environment files | .env.prod |
.env.prod + .env.prod.server |
| Migrations | in-process, on pia-server startup |
the pia-migrate one-shot, gating pia-server |
| Files on the host | compose, Caddyfile, init-db.sh |
those three + temporal-dynamicconfig.yaml |
| Compose invocation | plain -f docker-compose.prod.yml |
--env-file .env.prod -f docker-compose.prod.yml |
| Services | 7 | 12, four of which are one-shots that exit |
-
Back up.
Terminal window cd /opt/piacp .env.prod .env.prod.backupdocker inspect --format '{{index .RepoDigests 0}}' ghcr.io/pia-ai-dev/pia-server:latestdocker compose -f docker-compose.prod.yml exec postgres pg_dumpall -U pia > /root/pia-preupgrade.sqlKeep the digest. It is what a rollback pins to, since the tag is
:latest. -
Create
.env.prod.server. Copy.env.prodwholesale, then deletePOSTGRES_PASSWORD,VECTORDB_PASSWORD,APP_SECRET,UMAMI_DB_USERandUMAMI_DB_PASSWORDfrom the copy. By subtraction rather than by retyping: an include-list quietly drops whatever else the host holds —Admin__Emails(a lockout, the admin console being OAuth-only),OAuth__*,Ai__*,Knowledge__IngestApiKey. The five below are the required floor, not the whole file:Terminal window cat > /opt/pia/.env.prod.server << 'EOF'ASPNETCORE_ENVIRONMENT=ProductionDatabase__Provider=postgresqlDatabase__ConnectionString=Host=postgres;Database=pia;Username=pia;Password=<db-owner-password>Jwt__SecretKey=<existing value from .env.prod>Encryption__MasterKey=<existing value from .env.prod>Knowledge__ConnectionString=Host=pia-vectordb;Database=pia_knowledge;Username=pia;Password=<vectordb-password>EOFchmod 600 /opt/pia/.env.prod.serverReuse the existing
Encryption__MasterKeyverbatim. A new one makes every encrypted record unreadable. Leave.env.produntouched for now — step 7 trims it, and only after the new compose file is running. -
Add
TEMPORAL_DB_PASSWORDto.env.prod.Terminal window openssl rand -base64 24 # then add TEMPORAL_DB_PASSWORD=<value> to /opt/pia/.env.prodThis key has no fallback in the compose file. Unset,
temporal-db-initcreates the Temporal role with an empty password and the Temporal server never connects. -
Get the new files onto the host. Merging to
masterdoes this for you — the workflow copies all four. By hand:Terminal window scp docker-compose.prod.yml temporal-dynamicconfig.yaml root@<server-ip>:/opt/pia/Caddyfileandinit-db.share unchanged and already there.temporal-dynamicconfig.yamlis new and mandatory: it is bind-mounted as a file, so if it is missing Docker creates a directory in its place and the Temporal server exits onunable to create dynamic configbefore opening a port. -
Check the Postgres mount path. The compose file points
postgresat/mnt/HC_Volume_104717731/postgres. If that is not this host’s mount, edit the line before starting — Docker would otherwise create the path on the boot disk and Postgres would initialize an empty cluster there, next to your untouched data. -
Deploy.
Terminal window cd /opt/piadocker compose --env-file .env.prod -f docker-compose.prod.yml pulldocker compose --env-file .env.prod -f docker-compose.prod.yml up -d --force-recreate -
Verify, then trim
.env.prod.Terminal window docker compose --env-file .env.prod -f docker-compose.prod.yml ps -adocker compose --env-file .env.prod -f docker-compose.prod.yml logs pia-migratecurl https://cloud.pia.de/healthExpect
pia-migrate,temporal-db-init,temporal-schema-initandtemporal-namespaceatExited (0)— that is their finished state, not a failure, and they stay indocker ps -afrom here on. Everything else should be up, withtemporalhealthy.Once
/healthreturns 200, delete the application keys from.env.prod, leaving the infrastructure ones.postgresandumamiread nothing else from that file, so nothing needs recreating. -
Optional: turn on the operator runtime. The Temporal services run either way, but the worker stays off until you add four keys to
.env.prod.server— see the operator runtime.
Rolling back
Section titled “Rolling back”Restore .env.prod from .env.prod.backup, put the previous docker-compose.prod.yml back, and start it against the digest recorded in step 1. Schema migrations are not reverted, and this jump is not purely additive: DropGuardrailContentPreview removes guardrail_decisions.ContentPreview, which the older image still selects, so its guardrail reads fail until the dump from step 1 is restored. The rest are additive and nullable and the old server ignores those columns. Either way, a rollback that has already served traffic is better resolved forward. .env.prod.server can stay on disk; the old compose file ignores it.
Tokens become credits
Section titled “Tokens become credits”A separate, later jump, and one that applies to every host rather than only pre-pia-migrate ones. The
AddCreditAccounting migration changes the unit group limits are denominated in, and it converts what is
already there — no operator action is required, but two things move under you:
- Every group limit is divided by 1,000 and rounded up. A group on
1000000weekly tokens comes out at1000weekly credits, which is the same traffic at the default provider rate. A group whose settings carried no limits at all stays unlimited, exactly as before. The sliding pool multiplier is seeded to0everywhere, so no group starts pooling until someone turns it on. - The shared pool is spent before anyone’s own allowance. Switching the multiplier on means the group’s carried-over credits go first and members’ own weekly counters do not move until the pool is empty. One consequence to price in: because pool draws do not count as own use, a group whose weekly spend stays inside its pool renews the same pool every week instead of burning it down. It never grows, and the per-week total stays inside the baseline.
- The weekly counter switches from a rolling seven days to a calendar week, anchored at Monday 00:00 in
TokenUsage__WeekStartTimeZone(defaultEurope/Berlin). This is the boundary the weekly429always claimed. On the first Monday after deploy, every member with weekly spend behind them gets it back at once rather than a day at a time — plan the deploy for a Monday if that matters to you.
Historic usage rows are re-priced at the anchor rate using the cache-hit multiplier each row was charged at, so the past reads the same as it did. Rate cards on existing providers start at the anchor, and a provider that carried a cache-hit multiplier keeps its discount: the multiplier was a fraction of an input token, so its cached input rate becomes that fraction of the input rate. Nothing needs re-entering.
- Production deployment — the full reference for a fresh host, including the database role split
- Configuration — every key both environment files accept