Releases & upgrades
Flagpost ships as tagged releases with generated notes and, since v1.1.1, pinned container images (v1.0.0, the initial public release, shipped in July 2026). Follow Releases for what changed; security fixes are additionally published as GitHub Security Advisories.
Versioned images (pull instead of build)
Section titled “Versioned images (pull instead of build)”Every release publishes reproducible images to GHCR:
ghcr.io/tbcsec/flagpost-backend:vX.Y.Z (also :latest)ghcr.io/tbcsec/flagpost-frontend:vX.Y.Z (also :latest)The release frontend is built in same-origin mode — API and WebSocket
calls resolve against whatever origin serves the page — so one image works
behind any single-origin proxy with no baked-in domain and no
PUBLIC_ORIGIN rebuild. Point the compose backend/frontend services
at the images (a two-line override) and upgrading becomes changing a tag:
services: backend: image: ghcr.io/tbcsec/flagpost-backend:v1.4.0 build: !reset null frontend: image: ghcr.io/tbcsec/flagpost-frontend:v1.4.0 build: !reset nullWhich version am I running? A release image reports its exact tag; a
source build reports the release it’s based on with an -src suffix
(e.g. 1.4.0-src), since main accumulates the next version as soon as a
tag is cut.
Upgrading
Section titled “Upgrading”Database migrations run automatically when the backend starts, so the procedure is short:
- Read the release notes for the versions you’re crossing — they call out
anything upgrade-relevant (v1.2.0, for example, notes that scores
inflated by the fixed submission race are corrected by a dedupe
migration, and that reachable deployments must set real object-storage
credentials or the backend refuses to boot; v1.3.0’s migration
transparently encrypts any stored SMTP password — no action under
compose, but running without Docker make
sure the
SECRET_ENCRYPTION_KEYor its key file is in place before migrations run — and platform exports taken on v1.3.0+ no longer contain the SMTP password, so re-enter it after restoring one. v1.4.0’s callouts: custom roles that toggled per-competition modules throughedit_competitionmust now be granted the newmanage_modulespermission — built-in roles pick it up automatically at startup; the compose now binds the object store’s S3 API to loopback, so multi-host deployments relying on the previously published:9000must publish it deliberately; and the new AI assistants module is off by default — enabling it sends chat content to the operator-configured provider endpoint. And v1.7.0 retires MinIO for RustFS and renames everyMINIO_*variable toS3_*as a hard break — the backend refuses to start while anyMINIO_*remains set; see the object-store migration below. v1.7.1 is a security patch that multi-worker deployments in particular should take, and v1.8.0 has a short list of operator notes). - Take a platform export and a
pg_dump. - Bump the image tag (or
git pull+docker compose buildfor source builds), thendocker compose up -d. - Confirm the version in the admin footer/settings and watch
/api/health.
Skipping versions is fine — migrations apply in sequence.
Upgrading to v1.8.0
Section titled “Upgrading to v1.8.0”v1.8.0 adds programming challenges and the code judge — an optional module that does nothing until you set it up — and includes security fixes; see the security advisories. For most installs it’s the usual procedure above: the database migrations (the judge’s new tables, and the emailed-link change below) run automatically when the backend starts. Check whether any of these apply to you:
- Kubernetes challenge instances — re-apply the RBAC manifest so the
namespace picks up the updated baseline network policy:
kubectl apply -f k8s/instances-rbac.yaml. Test connection’s new baseline policy check stays red until you do. If your cluster’s DNS isn’t the standard kube-dns pods inkube-system, see the note under Bootstrap. - Emailed links — email-verification and password-reset links sent before the upgrade stop working. Users simply request a new one.
- SAML — with an identity provider on a different site, Flagpost must now be served over HTTPS; over plain HTTP, SAML works only with an IdP on the same site. See SAML 2.0 setup.
- Launching uvicorn yourself — if you start the backend with your own
command (a custom Fargate task, a
non-Docker install) rather than the shipped
images or compose, add
--ws-max-size 2097152. - Marketplace registry mirrors — a mirror must serve responses uncompressed or with a single gzip or zlib-wrapped deflate encoding; raw deflate, Brotli, zstd and stacked encodings are refused. Each artifact must also arrive within 200 MiB and five minutes.
- Delegated administration — if you hand out custom roles rather than the built-in Administrator: changing the mail server settings and exporting user accounts now need the full Administrator permission set; each backup section is offered only to holders of the permissions its content needs; a delegate can’t manage accounts holding permissions they lack; and a site-wide role with only global permissions no longer lists private competitions it isn’t part of. Built-in roles see no change. Details in Users & roles.
Swapping MinIO for RustFS (v1.7.0)
Section titled “Swapping MinIO for RustFS (v1.7.0)”v1.7.0 retires MinIO for RustFS as the default object store
(ADR-0041),
with vendor-neutral S3_* configuration. It’s a hard break: the
MINIO_* variables were renamed — MINIO_ROOT_USER→S3_ACCESS_KEY,
MINIO_ROOT_PASSWORD→S3_SECRET_KEY,
MINIO_PUBLIC_ENDPOINT→S3_PUBLIC_ENDPOINT, MINIO_ENDPOINT→S3_ENDPOINT,
MINIO_REGION→S3_REGION, MINIO_IAM_AUTH→S3_IAM_AUTH — and the backend
refuses to start while any MINIO_* variable is still set, printing the
mapping, so a stale .env fails safe instead of silently falling back to
default credentials.
Objects are not migrated automatically. Do the upgrade in this order:
- With the old stack still running, copy the objects out —
rclone syncfrom the oldminio-dataserver to a scratch location (or straight to the new server once it’s up). docker compose down --remove-orphans. The oldminiocontainer is gone from the compose file, so a plainup -dwould leave it running and holding port 9000, andrustfswould fail to bind.- Rename the variables in
.env, thendocker compose up -d. - Sync the objects into the new
rustfs-dataserver, then drop the old volume:docker volume rm <project>_minio-data.
The update check
Section titled “The update check”Once every 24 hours the backend makes exactly one outbound request:
GET https://updates.flagpost.io/v1/check?version=<your-version> → {"latest": "x.y.z"}The version number is the entire payload — no install identifier, no hostname, no counts, no personal data; there is no field for any of it. The response drives the admin “update available” notice, and counting the requests is the project’s only measure of how many deployments exist (deliberately without unique-install tracking, which would require an identifier). The full design is in PRIVACY.md.
It never renders remote content (only a version string is compared), never fails loudly (air-gapped is a normal condition, not an error), and never runs in demo mode.
Turning it off is a supported configuration, not a grudging one:
- Admin → Settings — toggle update checks off, or
- set
UPDATE_CHECK_URL=(empty) in the environment, and an air-gapped install never attempts the call at all.
Staying informed without the check
Section titled “Staying informed without the check”If you disable it, watch the GitHub Releases feed and security advisories — security releases (like v1.2.0) are the ones not to sit on.