Self-Hosting

Upgrading

Upgrading

Knowing an upgrade exists

Your instance can tell you. In the admin panel, Instance → About shows the version you are running and, when you press Check for updates, compares it against the current release.

Nothing about that is automatic. There is no background poll and no telemetry: the instance makes no outbound request until an administrator presses that button, and the request itself is a plain fetch of a public version number carrying no identifier, no licence and no usage data. An instance with no internet access simply reports that it could not reach the listing, and carries on.

If you would rather it never reach out at all, set DOCUNEXUS_UPDATE_CHECK_URL= (empty) in .env. The button disappears.

Back up first

Every time. No exceptions. Migrations run automatically and they run forward only.

cd docu.nexus
mkdir -p backups
docker compose exec -T postgres \
  pg_dump -U docunexus --no-owner --no-acl docunexus \
  | gzip > backups/docunexus_$(date +%Y%m%d_%H%M%S).sql.gz

See Backup and Restore for the storage volume, which that command does not touch.

What the container does at start

An entrypoint script inside the container runs before the server. In order:

  1. Parses the host out of DATABASE_URL and blocks on pg_isready until Postgres answers. There is no timeout. If Postgres never comes up, the container sits here.
  2. If MIGRATE_ENABLED is true (the default, and what the production compose sets), it runs the migration runner against the bundled migrations directory. Kysely applies every migration that is not already recorded in the migration table, in order, and prints one line per migration.
  3. If SEED_ENABLED is true, it seeds demo data. The production compose sets it to false. Leave it there.
  4. If SEED_EXAMPLE_CONTENT is true (the default), it runs the example-content seeder. That step is idempotent and never fatal: it warns and carries on. It creates no accounts, so on an instance that has not been through the setup screen yet it reports that there is no administrator to attribute pages to and leaves the job to the wizard.
  5. execs the server process.

A failed migration is fatal. The runner exits non-zero, set -e aborts the entrypoint, and the container dies rather than serving against a half-migrated schema. That is deliberate. Read the logs, fix the cause, restore if needed.

Doing the upgrade

The upgrade is the same command that installed the instance, run again from inside the install directory. You are already there from the backup above. It is idempotent and safe to re-run.

curl -fsSL https://docu.nexus/install.sh | sh

From anywhere else, name the directory rather than changing into it:

curl -fsSL https://docu.nexus/install.sh | DOCUNEXUS_DIR=/path/to/docu.nexus sh

It keeps .env and every volume, refreshes docker-compose.yml (keeping your copy as docker-compose.yml.bak if you had edited it), downloads the new image only if that version is not already loaded, verifies its checksum, and restarts the stack.

To watch what it did:

docker compose logs -f app

Watch for Migrations completed! followed by Starting application.... Then confirm the app is answering:

curl -f https://docs.example.com/health

Expect {"status":"ok","timestamp":"..."}.

Postgres and Redis are not recreated unless their image tags changed, so the restart only cycles the app container. Expect a few seconds of downtime while migrations run, longer on a large database if the release includes a table rewrite.

Upgrading without internet access

The installer downloads one file. On an air-gapped host, fetch it somewhere else and carry it over:

# on a machine with internet access
VERSION=$(curl -fsSL https://docu.nexus/dl/latest)
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
curl -fLO "https://docu.nexus/dl/docunexus-$VERSION-$ARCH.tar.gz"
curl -fLO "https://docu.nexus/dl/docunexus-$VERSION-$ARCH.tar.gz.sha256"

Match the architecture to the target host, not the one downloading. Then, on the server:

sha256sum -c docunexus-<version>-<arch>.tar.gz.sha256   # must say OK
docker load -i docunexus-<version>-<arch>.tar.gz
cd docu.nexus
# point .env at what you just loaded
sed -i 's|^APP_IMAGE=.*|APP_IMAGE=docu.nexus:<version>|' .env
docker compose up -d

Verify the checksum. It is the only integrity guarantee on a plain HTTPS download; a registry would give you content addressing for free and this path does not.

Pinning a version

APP_IMAGE in .env selects the image, and the installer rewrites it on every upgrade to whatever it just loaded. Edit it by hand to hold a version deliberately, and stop running the installer — it would move you forward again.

APP_IMAGE=docu.nexus:v1.4.0

Rolling back

Rolling back is a database restore plus an image pin. Do not try to roll back the image alone: the new schema is already applied, and the old code will not understand it.

docker compose stop app

# 1. Point .env back at the previous image, BEFORE anything starts, and put
#    back any setting the upgrade asked you to change (see below).
$EDITOR .env          # APP_IMAGE=docu.nexus:<previous version>

# 2. Restore the pre-upgrade dump (see Backup and Restore for the full sequence).
docker compose exec -T postgres psql -U docunexus -d postgres -c "DROP DATABASE IF EXISTS docunexus;"
docker compose exec -T postgres psql -U docunexus -d postgres -c "CREATE DATABASE docunexus OWNER docunexus;"
gunzip -c backups/docunexus_<pre-upgrade>.sql.gz \
  | docker compose exec -T postgres psql -U docunexus -d docunexus --quiet

# 3. Only now bring the old image up.
docker compose up -d app

The dump restores the database and nothing else. It does not restore .env, and any setting there that the old version reads has to hold the value it had before the upgrade. In particular, if .env set LDAP_SIGNIN_CONNECTIONS before the upgrade, it must hold exactly that value when the old image starts, and if it was unset it must be unset. Older versions read it before the sign-in settings stored in the database, so any other value changes which directory connections sign people in: some people are locked out, and others who were kept out can sign in. This is why LDAP and Active Directory Setup says to leave it alone until the release that removes it.

The old image has to still be on the host. docker images lists what you have; the installer does not delete previous versions, so unless you pruned them the one you want is there.