Releases: heyvaldemar/forgejo-traefik-letsencrypt-docker-compose
Release list
v1.0.4
Fixed
- CI had never run the restore script. The test restored with its own
copy of the commands. The script readDATA_PATHandDATA_BACKUPS_PATH
from the shell that ran it rather than from.envor the stack, so a path
set in.envwas not the one it listed or cleared, and it cleared with
rm -rf dir/*, which leaves every dotfile of the newer state in place. It
now takes every path and name from the running backups container, accepts
the backup file name as an argument, starts the application again whatever
happens, and CI runs it: a file written before a backup and deleted after it
must be back once that backup is restored.
Changed
- The freshness check has its own workflow, Pin Freshness. It ran inside Deployment Verification, whose badge is the one at the top of this README. Across the fleet, nine red runs in ten were a pin one version behind - which the fleet's triage moves within the day - and a reader cannot tell that from a stack that does not boot. The badge now says whether the stack boots. The job itself is unchanged.
v1.0.3
Security
traefik:3.7was rebuilt upstream; the pin moved fromsha256:1c32e7c36820…tosha256:24841fe2de73…. Same version, same tag, a rebuilt base image — the usual shape of a security fix in a base layer.
Upgrading
git pull (or ./update.sh), then docker compose up -d. Containers on a refreshed image are recreated; data volumes and .env are untouched. This release was cut by fleet triage after the deploy job booted the stack on the refreshed images.
Full history in CHANGELOG.md.
v1.0.2
Security
alpine:3.22was rebuilt upstream; the pin moved fromsha256:365499d9dccb…tosha256:5291449c3df7…. Same version, same tag, a rebuilt base image — the usual shape of a security fix in a base layer.
Upgrading
git pull (or ./update.sh), then docker compose up -d. Containers on a refreshed image are recreated; data volumes and .env are untouched. This release was cut by fleet triage after the deploy job booted the stack on the refreshed images.
Full history in CHANGELOG.md.
v1.0.1
Changed
codeberg.org/forgejo/forgejo:16.0.4moved tocodeberg.org/forgejo/forgejo:16.0.5. The freshness check reported the lag; the deploy job booted the stack on the new image before this landed.
Security
traefik:3.7was rebuilt upstream; the pin moved fromsha256:f86a2cab1b5c…tosha256:1c32e7c36820…. Same version, same tag, a rebuilt base image — the usual shape of a security fix in a base layer.alpine:3.22was rebuilt upstream; the pin moved fromsha256:14358309a308…tosha256:365499d9dccb…. Same version, same tag, a rebuilt base image — the usual shape of a security fix in a base layer.
Upgrading
git pull (or ./update.sh), then docker compose up -d. Containers on a refreshed image are recreated; data volumes and .env are untouched. This release was cut by fleet triage after the deploy job booted the stack on the refreshed images.
Full history in CHANGELOG.md.
What upstream changed
Read by fleet triage from the upstream release notes (or the commits between the tags) against this compose file, before the bump was applied.
Upstream changes 16.0.4 -> 16.0.5
Verdict: NEEDS ATTENTION — release notes could not be retrieved, so the upgrade cannot be verified against upstream and must be checked by hand before applying.
Breaking changes
- Unknown. The upstream release notes were not available: "GitHub releases for codeberg.org/forgejo/forgejo could not be read: HTTP Error 404: Not Found". No UPGRADING, BREAKING_CHANGES or CHANGELOG excerpt was supplied either. Nothing here is invented in their absence.
Variables
- none confirmed — cannot check without the notes. The compose file's variable surface (
FORGEJO_IMAGE_VERSION,FORGEJO__*app.ini overrides, etc.) is unchanged in what was given to me, but that only means the template didn't change, not that Forgejo 16.0.5 doesn't require something new.
Data and dependencies
- none confirmed from notes. Worth checking by hand: this template pins SQLite (
FORGEJO__database__DB_TYPE: sqlite3), so there is no companion database engine version pin to cross-check here — the "no SAFE TO APPLY if a companion service version is unpinned" condition does not apply to this deployment.
Before applying
Since the notes are unavailable, treat this as an unreviewed upgrade:
- Read the actual Forgejo 16.0.5 release/changelog manually at https://codeberg.org/forgejo/forgejo/releases or the forgejo.org release blog before touching production.
- Back up the
forgejo-datavolume (the compose file's ownbackupsservice does this — trigger a manual run or confirm a recent archive exists underDATA_BACKUPS_PATH) before pulling the new image. - After confirming manually there's no breaking change, update the digest/tag: change
FORGEJO_IMAGE_VERSION(or thex-imagesdefault) from16.0.4@sha256:a3e33d0…to the correct16.0.5@sha256:…— do not float the tag without a digest. - Watch the healthcheck (
/api/healthz) afterdocker compose up -d; it distinguishes a real pass from a listening-but-broken container.
Notes read
- None. GitHub releases endpoint for codeberg.org/forgejo/forgejo returned 404. No UPGRADING/BREAKING_CHANGES/CHANGELOG text was provided for the 16.0.4 -> 16.0.5 range. Recommend fetching notes directly from Codeberg (not GitHub) — e.g.
https://codeberg.org/forgejo/forgejo/releases/tag/v16.0.5— before proceeding.
v1.0.0
First release. A production deployment of Forgejo behind Traefik with automatic Let's Encrypt TLS, built to the fleet standard.
The one test a git host actually has to pass
Forgejo comes up cleanly on an empty database — that is what a fresh install is. So every check that only asks whether it answers would also pass an instance that had lost everything.
The deploy job creates an admin from the CLI, creates a repository through the API, pushes a commit carrying a 5 MB blob over HTTPS through Traefik, requires the API to report that exact commit on main, clones it back and checks that the commit and the whole blob came with it. Then it takes a backup, restores from it, and asks for the same repository again — at the commit it was pushed at.
What it ships
Forgejo 16.0, Traefik, and a plain alpine for the backups sidecar, all three pinned to tag@sha256:<digest> as interpolation defaults. git pull alone delivers the tested combination.
No web installer, and no default password. Forgejo ships an installer and most compose files leave it on, which means whoever reaches a new hostname first configures the instance and becomes its administrator. On a public IP that window is minutes, not days: scanners watch certificate transparency logs. INSTALL_LOCK is set, the admin account is created from the CLI, and CI asserts that /install stays closed. Registration is disabled and nothing is visible without signing in, so this starts private and opens by decision.
SQLite, deliberately, against this fleet's own PostgreSQL pattern. It is officially supported and it is what upstream tests; choosing it removes a container, a second image pin, and a database version that would have to stay in lockstep with whatever takes the dumps. The cost is stated rather than hidden: SQLite is one writer at a time, and the README says where the line is and how to cross it.
Git over SSH as an opt-in override file. The base stack serves git over HTTPS, which needs no second published port and survives a proxy that terminates TLS. git-over-ssh.override.yml turns the built-in server on for people who want public keys, published on 2222 rather than 22 — port 22 belongs to the host's own sshd, and taking it from a machine you administer over SSH is a way to lose the machine.
A backup loop that reads its own archive back before naming it a backup. Each cycle writes .partial, verifies it with tar -tzf, and only then renames. BusyBox tar returns exit code 1 both for a file changed while I read it and for I could not write the output at all.
Three things the build taught, written into the file
The database path is not a free choice. The image starts as root, creates /data/git and /data/gitea, chowns those two to the uid it then drops to, and leaves /data itself owned by root. A database path anywhere else under /data cannot be created by the process that needs it: the container comes up, retries ten times with mkdir /data/…: permission denied, and never turns healthy. It works on a bind mount chowned by hand, which is exactly why it is pinned down in the compose file — a setting that is fine on the maintainer's host and broken on a named volume is the kind that ships.
There is no Buffering middleware, and that is deliberate. Traefik streams request and response bodies unless one is attached; attaching it is what turns buffering on, and 0 on its limits means no size ceiling, not off. Measured against a response that takes four seconds to produce: 0.03s to the first byte without it, 4.15s with it. What limits a long push is the entry point's readTimeout, 60 seconds by default and set to zero here.
An archive taken a moment before a push cannot contain what the push delivered. The first CI run failed on exactly that. The generic test suite asserts the database by name; the repository store is asserted in the workflow, against an archive taken after the test push — a stronger claim than the original, and one that cannot race.
Also included
update.sh that moves between release tags and refuses to cross a major unattended, a restore script that stops the server first, container hardening, resource limits and reservations, a sixty-second stop_grace_period, .env.example, a bug-report form, Dependabot, and OpenSSF Scorecard.