Upgrading Specivo means moving to a newer specivo/specivo Docker image. The container entrypoint applies any new database migrations on startup automatically, so the process is straightforward: back up, pin the target version, pull, restart, and verify. This guide walks through an upgrade from 0.1.9 to 0.1.10 performed on the same kind of 1 vCPU / 2 GB Ubuntu 24.04 droplet used in the install guide.
The working directory throughout is /opt/specivo, which is where the install guide puts the Compose file, .env, and the specivo-data/ directory.
What you'll do
- Confirm the stack is healthy before touching anything
- Take a database dump and archive the data directory
- Pin the target version in
.env - Pull the new image and restart the stack
- Verify everything came up on the new version
Before you start
You need an existing Specivo install (the setup produced by the install guide), shell access as root, and a few minutes. Before you pull anything, read the release notes for the version you're moving to — the CHANGELOG is in the repository and linked from each GitHub release. Know what changed before you run the upgrade.
Step 1 — Check what you're running now
SSH into the server and switch to the install directory:
cd /opt/specivo
Confirm all containers are healthy:
docker compose ps
You should see six containers — nginx, api, db, redis, celery-worker, and celery-beat — all reporting Up (healthy). If any container is restarting or unhealthy, resolve that before proceeding.
Check the current schema revision:
docker compose exec -T api alembic current
Note the output. In this walkthrough it was 0020 (head). You'll check this again after the upgrade.
Check disk space. The pull will download a new image layer and the backup archive will sit on disk until you move it off the host:
df -h /
The droplet used here showed 43 GB free on a 48 GB disk — comfortably enough for the pull plus a backup archive that landed at about 280 MB.
Step 2 — Back up first
This step is mandatory every time you upgrade. Migrations can change the database schema and are not always reversible. If an upgrade goes wrong after a migration has already run, your backup is the only way back. Do not skip it.
Create a directory to hold backups if you don't have one already:
mkdir -p /opt/specivo-backups
Dump the database. The -T flag is required to pipe output correctly without allocating a pseudo-TTY:
docker compose exec -T db pg_dump -U specivo specivo \
> /opt/specivo-backups/backup-$(date +%Y%m%d-%H%M%S).sql
The dump produced here was about 210 KB — a complete plain-SQL snapshot of every issue, wiki page, comment, and history record.
Archive the data directory. This captures attachments, logs, themes, the bundled database files, and the embedding model on disk:
tar czf /opt/specivo-backups/specivo-data-$(date +%Y%m%d-%H%M%S).tar.gz specivo-data
The archive here was about 279 MB, most of which is the ~400 MB embedding model already compressed on disk plus the PostgreSQL data files.
Move both files off the host — to object storage, an external drive, or a remote server — before continuing. A backup that lives only on the same machine you're upgrading is not a real backup.
Step 3 — Pin the target version
Open .env in your editor and change SPECIVO_VERSION from the current value to the release you want:
SPECIVO_VERSION=0.1.10
If your .env still has SPECIVO_VERSION=latest from the initial install, this is the right moment to switch. Pinning an explicit tag means you decide when each upgrade happens, and you always know exactly what image is running. Rolling back is equally simple: re-pin the old tag.
Step 4 — Pull the new image
docker compose pull
Compose fetches only the layers that changed. The pull ends with a line like:
Image specivo/specivo:0.1.10 Pulled
The db (pgvector), redis, and nginx images are not part of the Specivo release cycle, so unless you changed their pinned versions they won't be re-pulled here.
Step 5 — Restart the stack
docker compose up -d
Compose compares the running containers against the Compose file and recreates only the ones whose image changed. In this upgrade that was three containers: specivo-api-1, specivo-celery-worker-1, and specivo-celery-beat-1. The db, redis, and nginx containers were left untouched because their images didn't change.
Compose waited for db and redis to report healthy before starting api — you don't need to manage that ordering yourself.
Migrations run automatically. The entrypoint runs alembic upgrade head on every startup. You will see lines like this in the API log:
Running database migrations...
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
When a release does include schema changes, you'll also see Running upgrade <rev> -> <rev> lines between those — alembic applying each new migration in order.
You never run a migrate command by hand. Watch the log to confirm a clean start:
docker compose logs -f api
Press Ctrl-C to stop following when you see Application startup complete.
One thing to be aware of: this particular hop from 0.1.9 to 0.1.10 added no new schema migration — alembic current was 0020 (head) both before and after. The entrypoint still ran the migration step, which is idempotent. Other version hops may include schema migrations, which is exactly why the pre-upgrade backup is mandatory every time regardless of whether you expect schema changes.
Step 6 — Verify
Confirm all containers are on the new image:
docker ps
The api, celery-worker, and celery-beat rows should all show specivo/specivo:0.1.10. The api container should report Up (healthy).
Check that the login page responds:
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9933/
A 302 redirect to the sign-in page is the expected healthy response.
Verify the schema revision is still consistent:
docker compose exec -T api alembic current
Finally, open the app in a browser, sign in, open an issue, and run a search. If all three work, the upgrade is done.
Rolling back
If the new version misbehaves, roll back by re-pinning the previous tag in .env:
SPECIVO_VERSION=0.1.9
Then pull and restart:
docker compose pull
docker compose up -d
If the failed upgrade already applied a schema migration, re-pinning alone is not enough. The database schema may be ahead of the older image, and the older image will refuse to start or behave incorrectly. In that case you must restore the pre-upgrade database dump and the
specivo-data/archive, and then start the older image. Restore the data directory first, then load the SQL dump into the database:docker compose down tar xzf /opt/specivo-backups/specivo-data-<timestamp>.tar.gz docker compose up -d db docker compose exec -T db psql -U specivo specivo \ < /opt/specivo-backups/backup-<timestamp>.sql docker compose up -dThis is exactly why the pre-upgrade backup is mandatory every time.
Good habits
- Upgrade one minor version at a time rather than jumping several releases at once. The migration sequence is designed to run incrementally.
- Read the release notes for every version before you pull.
- Back up every time, even for releases that appear to be patch-only.
- Keep
SPECIVO_VERSIONpinned to an explicit tag in.env. Never run a production instance onlatest. - After every upgrade, do a quick smoke test: sign in, open an issue, run a search.