MILL_SECRET and runtime settings securely. Protected database values require that secret even after a successful restore. Record both API/UI versions or digests, the Compose project name and PostgreSQL major version with each backup.
Back up an existing or managed database
Use your PostgreSQL provider’s supported backup and restore procedures, or PostgreSQL’spg_dump and pg_restore against the database in DATABASE_URL. The backup must cover the complete Mill database, not just task tables. Rehearse restoration into a separate database, configure a separate Mill API/UI pair with its connection URL and the original MILL_SECRET, and verify the restored workspace before relying on the backup.
Back up bundled PostgreSQL
If you installed the optional PostgreSQL Compose overlay, use its packagedpg_dump. You do not need Git, Node or a source checkout:
pg_dump takes a consistent database snapshot while ordinary writes continue.
Encrypt backups and a separate copy of the runtime secrets, store a copy off the Docker host and periodically rehearse recovery. Keep enough free space for both the archive and a separate restored database.
Rehearse bundled PostgreSQL recovery
Use the same compatible API and UI images as the backup. Copy the installation’s two Compose files and.env into a separate recovery directory. In the copied docker-compose.yml, change the web service’s host port from 127.0.0.1:4321:4322 to 127.0.0.1:4322:4322. In the copied .env, set MILL_BASE_URL=http://localhost:4322 and preserve MILL_SECRET, POSTGRES_PASSWORD and the private PostgreSQL URL. Keep the copy private. The separate project name below creates its own PostgreSQL volume.
Run these commands from that recovery directory:
Recover the running installation
Put the public UI into maintenance. Back up the existing database before replacing it, then stop bothapi and web. Restore to a separate project first using the compatible images, verify it, and switch the HTTPS proxy to its UI. Retain the original database until the recovered installation is confirmed.
If you deliberately need to replace an existing database, use PostgreSQL’s documented restore process or the reviewed repository helper with explicit project confirmation. Do not add --clean casually to a restore command: it removes existing database objects.
Optional repository helpers
Operators using bundled PostgreSQL who keep a source checkout can use the guarded scripts. They create private backup files, reject overwrite, validate the archive and require explicit target confirmation:recovery.env identifies the separate bundled project; if its copied Compose file has a different port, use the direct recovery commands above. The restore helper stops both API and UI, rejects a nonempty target unless --replace is explicitly supplied, restores in one transaction, and starts both services only after success. A failed restore leaves them stopped. These helpers are optional; a published-image installation does not require them.
See upgrades for schema compatibility and private pre-launch conversion. Historical portable JSON exports have no importer in v1; retain them separately if needed, but use database backups for supported recovery.