Skip to main content

Update AFCT

Create or confirm a current backup before applying an update.

Guided-installer deployment

On Linux or macOS, open the directory that contains docker-compose.yml and run:

sh install.sh update

This records the currently deployed image versions, pulls the latest images, recreates the stack, and waits for the health check. If the new version does not become healthy, it automatically rolls back to the previous images and restores service.

Update the installer itself

sh install.sh update uses the docker-compose.yml already on the host. Use self-update when you want to refresh the installer script itself:

sh install.sh self-update
sh install.sh update

self-update downloads the installer, docker-compose.yml, and the environment template from the repository, backing up the old copies first. It never touches .env.production or application data. It needs no Git checkout because the files come from the public repository over HTTPS.

A changed docker-compose.yml no longer has to be refreshed by hand before an update: an in-app upgrade applies the target release's compose file for you (see In-app updates below). This host-side path stays available for deployments that do not run the updater, or as a fallback.

The equivalent Docker commands work on any platform, including Windows PowerShell:

docker compose pull
docker compose up -d

docker compose pull downloads the images named in the Compose file. docker compose up -d recreates only the services whose image or configuration changed. Unlike sh install.sh update, these commands do not verify health or roll back automatically.

Git-based manual deployment

Open the cloned AFCT repository and run:

git pull
docker compose pull
docker compose up -d

The commands are the same on Linux, macOS, and Windows PowerShell.

Named volumes preserve the database, uploaded files, backups, and certificates while containers are replaced.

Verify the update

sh install.sh update already waits for the health check, but you can confirm at any time:

sh install.sh status

Or with Docker directly:

docker compose ps
docker compose logs --tail=100 app

Confirm that every service is running and the application reports healthy. Then sign in and open an administration page.

In-app updates

AFCT can upgrade and downgrade itself from Admin Menu > System Settings > Updates without a shell session. A separate privileged updater service handles the operation, so the application container never touches Docker directly. The updater holds the Docker socket and is therefore off by default.

Enable it once, on the host, in the directory that holds docker-compose.yml:

sh install.sh enable-updater

(Or pass --with-updater during the initial sh install.sh install.) To turn it back off:

sh install.sh disable-updater

Once enabled, the Updates tab lists the available versions from the project's release manifest. Pick a newer version to upgrade: the updater takes a database backup first, swaps to the new images, waits for the whole stack to become healthy, watches it briefly to be sure it stays healthy, and rolls back automatically if it does not. Each successful upgrade records a restore point for the version you left, so you can downgrade back to it later.

warning

Downgrading restores the database from the backup taken at that restore point, which discards database records created since. Uploaded files are not rolled back and may become unreferenced. Only downgrade when you accept that result. The Updates tab requires explicit confirmation.

Before it restores, the updater takes a fresh backup of the current state so the downgrade is itself reversible. If that safety backup cannot be confirmed, the downgrade is refused rather than run: a downgrade discards the current database, so losing the snapshot would make the current state unrecoverable. Nothing is changed when it is refused, so the app stays up. The Updates tab then offers a Downgrade without a safety backup action that proceeds anyway; use it only if you accept that the current state cannot be recovered afterward.

Each restore point also has a Delete button. Removing one deletes its backup file to reclaim disk and drops it from the list, so you can no longer downgrade to that version. It does not affect the running application.

Only versions listed in the curated release manifest can be selected; the updater validates every request against it, so the app can never be pointed at an arbitrary image. Verification fails closed: if the manifest cannot be consulted at all (the server is offline and there is no local copy), the upgrade is refused rather than run unverified, unless the deployment has explicitly opted into allowing unlisted tags. When a release also changes the stack configuration, the compose file the updater downloads is checked against a checksum recorded in the manifest before it is applied, so a corrupted or tampered download is rejected and the current configuration is kept.

Every upgrade, downgrade, and update-service change is recorded in Admin Menu > System Settings > System Logs: one entry when it is requested and one for the outcome (completed, rolled back, or failed), so you can review what happened after the fact even once the live progress has cleared.

What counts as a healthy upgrade

An upgrade is only committed once the whole stack is confirmed good, not just the application container:

  • Every recreated service must be up, and any service that defines a health check (the app, the web front, the backup sidecar) must report healthy. The web front's health check also proves it can reach the app. The evaluator worker is a background process with no health check, so "running" is enough for it; a deployment that health-checks every service can require them all with UPDATER_REQUIRE_HEALTHCHECKS=true.
  • A stability window (default 45 seconds, UPDATER_STABILITY_SECONDS) follows: the updater keeps watching after everything is healthy, so a version that comes up and then crash-loops is caught and rolled back rather than committed. A service that exits, or the app drifting off the new version, ends the window early and rolls back.
  • Transient network failures are retried. Downloading the images and fetching a release's manifest or compose file are retried a few times with a short backoff (UPDATER_PULL_RETRIES, UPDATER_FETCH_RETRIES), so a momentary blip does not fail an upgrade. Destructive steps (database restores, the version swap, recreating containers) are never blindly retried; those roll back instead.

Stack changes are applied for you

Some releases change more than the application image: they add a service, a health check, or a setting in docker-compose.yml, or they update the updater component itself. The Updates tab handles these without a shell session:

  • Compose changes. During an upgrade, the updater fetches that release's docker-compose.yml from the release's own tag, validates it, and installs it (keeping a backup) before recreating the stack. If the upgrade has to roll back, the previous compose file is restored with it. A release whose compose needs a setting your host does not provide is left in place and the upgrade proceeds on the current configuration, so a bad file can never take the stack down.
  • The updater itself. Because the updater cannot recreate its own container, it tracks the application version separately. When it falls behind, the Updates tab shows an Update the update service action that brings it up to the running version. The update service restarts itself as part of this: it downloads the new version, hands off the swap, and stays in an in-flight state until the replacement update service comes back up and confirms it is actually running the new version. Only then is it reported updated. If the replacement does not come back on the expected version, that is reported rather than a false success, so a failed swap is visible instead of silently "done". A brief unavailability during the swap is expected and is not an error.

This means the console is normally not needed to keep a deployment current. The host-side sh install.sh self-update remains available as a manual path and as the way to update the installer script, but routine upgrades, including ones that change the stack layout, can be done entirely from the Updates tab.

Recovery after an interruption

An upgrade can be interrupted partway through: the server reboots, Docker restarts, or the updater container is recreated while it is working. The updater is built to recover from this on its own, so an interrupted upgrade does not leave the deployment in an unknown state.

While an upgrade runs, the updater keeps a small transaction record in its shared volume: the previous version, the exact local images the stack was running, whether it has changed the environment or compose files yet, and how far it has got. When it starts, it looks for an unfinished transaction and reconciles it against what Docker is actually running:

  • The new version is up and healthy. The upgrade effectively finished before the interruption, so it is committed. The updater does not roll a healthy new version back just because it was interrupted late.
  • The new version is not confirmed healthy. The updater returns the deployment to the previous version. It compares the running container's actual image against the requested one rather than trusting the version pinned in the environment file, so an upgrade that was interrupted right after the version was written is not mistaken for a finished one.

Rollback does not depend on the registry, the network, or the old tag still existing remotely. The previous version's images are kept on disk (superseded images are pruned only after a new version is confirmed healthy), and rollback reuses those exact local images. If the previous tag is missing, the updater re-points it at the image it recorded before the upgrade. This is why a rollback still succeeds when the machine is offline or the old release has been removed from the registry.

If both the upgrade and the rollback fail, the updater stops and reports that manual recovery is required rather than guessing. The status message and the System Logs entry name the versions involved so an administrator can restore from a restore point (see Backups). An interrupted downgrade is never resumed automatically, because it restores the database and re-running it could apply that restore twice; it is reported for manual recovery instead.