Upgrading and backups
Upgrade your deployment, back up the database, and plan recovery if an upgrade fails.
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.
bash
pg_dump --format=custom "$DATABASE_URL" > feedlog-$(date +%F).dump
You 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
bash
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 app
Vercel
bash
git pull upstream main
git push # Vercel rebuilds; migrations run in the build step
Cloudflare Workers
bash
git pull upstream main
pnpm install
pnpm build:cf && pnpm deploy:cf
# then visit the site as a signed-in admin — see below
Full 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 forDatabase migration completedin the logs before sending traffic to the new container./health
returns 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.
More in Workspace and self-hosting
Workspace settingsConfigure workspace branding, portal modules, members, permissions, and your portal address.Self-hosting overviewCompare Docker, Vercel, and Cloudflare Workers deployments and prepare your self-hosted FeedLog instance.Configuring external servicesConfigure OAuth sign-in, transactional email, file storage, and an AI model service for self-hosted FeedLog.