Upgrading and backups
An upgrade replaces the application code and may also change the database schema. You can restore the previous code by deploying the old image tag, but deploying an old image does not reverse schema changes.
Self-hosting overview explains when each platform runs migrations during a fresh installation. Upgrades use the same schedule, but must also preserve the data already stored in the database.
Back up before every upgrade
Back up Postgres. Boards, feedback, votes, comments, changelog entries, accounts, sessions and similarity embeddings are all stored there. Without a database backup, you cannot restore this data.
pg_dump --format=custom "$DATABASE_URL" > feedlog-$(date +%F).dumpYou can run this command while the application is running; no downtime is required. Test the backup by restoring it to a temporary database before using it as a recovery point.
Back up uploaded files separately. Their location depends on your storage configuration:
| Storage | Where files live | Risk during an upgrade |
|---|---|---|
S3-compatible (S3_* set), Cloudflare R2, or Vercel Blob | Your bucket or store | A deployment does not modify these files. Back them up separately. |
| Nothing configured (Docker) | Inside the container, under /app/.data | Removing the container removes the files unless this path is mounted as a volume. |
If no S3_* variables are set, uploads use a local-filesystem driver that writes inside the container. The bundled compose.yml mounts a named volume at /app/.data, so these files persist when the container is removed. If you used the one-line docker run command without adding -v, the files do not persist, and the docker rm step in a Docker upgrade removes them.
Keep a copy of your environment variables. In particular, preserve BETTER_AUTH_SECRET: changing it signs out all users and invalidates pending password-reset and email-verification links.
Upgrading
docker pull ghcr.io/linkcraftstudio/feedlog:v0.2.0
docker stop feedlog && docker rm feedlog
docker run -d --name feedlog -p 3000:3000 \
... same env vars as before ... \
ghcr.io/linkcraftstudio/feedlog:v0.2.0
docker logs -f feedlog # wait for "Database migration completed"
# With the bundled compose file: edit the image: tag, then
docker compose pull app && docker compose up -d appgit pull upstream main
git push # Vercel rebuilds; migrations run in the build stepgit pull upstream main
pnpm install
pnpm build:cf && pnpm deploy:cf
# then visit the site as a signed-in admin — see belowFull per-platform instructions stay in the repository, versioned with the release you're deploying: Docker · Vercel · Cloudflare Workers
Which image tag to deploy
| Tag | What it points at |
|---|---|
latest | The most recent build of main. Changes when a new build is published. |
v0.2.0 | A tagged release. Never changes. |
0.2 | The newest patch in that minor line. Changes when a new patch is published. |
sha-1a2b3c4 | One exact commit. Does not change. |
You can use latest while evaluating FeedLog. In production, use a vX.Y.Z or sha- tag. Otherwise, running docker compose up -d after an unrelated host restart may deploy a new version and run migrations unexpectedly. The bundled compose.yml therefore uses a version tag.
Migrations during an upgrade
Migrations use the same schedule as a fresh installation, but an upgrade must account for existing data if a migration fails.
- Docker — the container applies pending migrations at startup. A Postgres advisory lock serializes them, so two containers starting at once won't apply the same migration twice; the second waits and then finds no pending migrations. The lock does not prevent an old application version from running against the new schema, so stop the old version before starting the new one instead of replacing replicas one at a time. Wait for
Database migration completedin the logs before sending traffic to the new container./healthreturns 200 when the process is running, which may happen before migrations finish. - Vercel — migrations run during the build, before the Nuxt build step. A migration failure stops the deployment, and the previous deployment continues serving, so the failed schema change does not reach production. This requires
DATABASE_URLto be set for both the Production and Preview scopes, and your Postgres to be reachable from Vercel's build machines. - Cloudflare Workers — a Worker has no startup phase, so migrations run on request. An upgrade therefore requires one manual step, described below.
Cloudflare Workers requires an admin to confirm the upgrade
After pnpm deploy:cf, if the release contains migrations that haven't been applied, the Worker puts the site into setup mode: every page request redirects to /setup, and every /api/ request returns 503 with NOT_INITIALIZED. The /setup page then attempts to run the migration. Because this is an upgrade rather than a first installation, the endpoint requires a signed-in admin. Anonymous callers receive "Administrator required". This restriction prevents visitors from triggering schema changes.
In setup mode, the Worker redirects every request except /setup, /api/auth/**, /health and static assets. This includes the pages that contain the sign-in dialog. If you are not already signed in as an admin when the new Worker goes live, you cannot sign in through the interface.
Sign in before you deploy
Open your instance and confirm you're signed in as an admin before running pnpm deploy:cf. The session cookie remains valid after the deployment. When you next load a page, the Worker redirects you to /setup, runs the migration, and returns you to the original page. If you deploy before signing in, the site only serves /setup, which returns "Administrator required".
If /setup does not continue, request /api/_migrate/status directly. It reports state, the number of applied migrations and the number of expected migrations.
Rolling back
To roll back the application code, deploy the previous image tag, redeploy the previous Vercel deployment from the dashboard, or check out the old commit and run pnpm deploy:cf again.
FeedLog does not support rolling back the schema. Migrations are recorded in a tracking table and only move forward; FeedLog ships no down-migrations. Past releases have dropped indexes and unique constraints and tightened columns to NOT NULL, and an older build starting against that schema either misbehaves or refuses to start.
Before rolling back, determine which case applies:
- The new version has a bug and applied no migrations. Deploy the old tag. No database restore is required. This covers most rollbacks.
- The new version applied migrations. Deploy the old tag and restore the Postgres backup you took before the upgrade. Data written between the upgrade and the rollback is lost, so upgrade at a quiet hour.
On Docker and Vercel, the deployment log shows which migrations ran. On Cloudflare, check /api/_migrate/status after the deployment.
Restore the database, not just the image
Rolling the container back to a previous tag does not undo a migration. If the new version migrated your schema, you need the database backup taken before the upgrade.
Common errors
The container starts, immediately exits and repeatedly restarts. A migration failed. The Database migration failed line in docker logs feedlog contains the specific error. The schema is partly applied at this point. Fix the cause before restarting, or restore the backup and try the upgrade again.
Pages return 500 right after a container restart, then recover. The process starts listening while migrations are still running. Wait for Database migration completed before sending traffic to the container. A 200 response from /health does not mean the schema is ready.
The Vercel deploy failed on the migrate step. The previous deployment continues to serve traffic. Common causes: DATABASE_URL isn't checked for the Production scope, or the database isn't reachable from Vercel's build IPs (a self-hosted Postgres behind a VPN won't be).
/setup returns "Administrator required" and you cannot sign in. The Worker was deployed before an admin session was established. Use a browser that still has a valid admin session, then reload /setup.
Uploaded images 404 after a Docker upgrade. The instance used local filesystem storage without a volume on /app/.data, so docker rm removed the files. They cannot be recovered. Configure S3_* storage before the next upgrade. See Configuring external services.
Everyone was signed out after the upgrade. BETTER_AUTH_SECRET changed, usually because the new deployment generated a new value instead of reusing the old one. Restore the original value to restore the existing sessions.