diff --git a/.env.example b/.env.example index ce10d499..56faca5e 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,10 @@ # Image version (only used by docker-compose.images.yaml — the pull-based stack) # ────────────────────────────────────────────── # Which published image tag to pull for api/dashboard/jobs. -# - Pin to a release (e.g. 1.4.2) in production so a new release never auto-breaks you. -# - "latest" = newest stable release. "edge" = bleeding edge (last main build). +# - "latest" (below) = freshest main build; it MOVES on every merge to main (rolling, may +# include unreleased code), so a later `pull` can shift you to a newer build unannounced. +# - For production, PIN an exact release (e.g. 1.4.2) so a merge to main never auto-breaks you. +# "1.4.2" / "1.4" / "1" are immutable, published on a v* git tag. # Rollback = set this back to the previous version, then `... pull && up -d`. CIO_VERSION=latest diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 8ccadebc..e4673ddf 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -124,15 +124,14 @@ jobs: uses: docker/metadata-action@v5 with: images: ${{ env.DOCKERHUB_USERNAME }}/${{ matrix.image }} - # Tag policy (stable-releases + edge): - # git tag v1.4.2 -> 1.4.2, 1.4, 1, latest (a real, stable release) - # push to main -> edge only (latest is NEVER moved by a merge) + # Tag policy: + # push to main -> latest (freshest main build; moves on every merge) + # git tag v1.4.2 -> 1.4.2, 1.4, 1 (immutable, pinned release) tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }} - type=raw,value=edge,enable={{is_default_branch}} + type=raw,value=latest,enable={{is_default_branch}} - name: Build and push ${{ matrix.name }} uses: docker/build-push-action@v5 diff --git a/apps/docs/content/docs/self-hosted/docker.mdx b/apps/docs/content/docs/self-hosted/docker.mdx index 722585af..c17a5bde 100644 --- a/apps/docs/content/docs/self-hosted/docker.mdx +++ b/apps/docs/content/docs/self-hosted/docker.mdx @@ -112,8 +112,8 @@ The script builds and starts Postgres, Redis, MinIO (object storage), the API, t ClassroomIO publishes versioned images. `CIO_VERSION` controls which tag you run: -- Pin an exact release (e.g. `1.4.2`) in production so a new release never restarts you onto changed images unattended. -- `latest` = newest stable release. `edge` = bleeding edge (last `main` build). +- Pin an exact release (e.g. `1.4.2`) in production so a merge to `main` never restarts you onto changed images unattended. +- `latest` = the freshest `main` build — it moves on every merge (rolling; may include unreleased code). Version tags (`1.4.2`, `1.4`, `1`) are the immutable, pinned releases. **Pre-built images path** — bump `CIO_VERSION` in `.env`, then re-pull and recreate: diff --git a/docker-compose.images.yaml b/docker-compose.images.yaml index cdbe9c51..a9bb65db 100644 --- a/docker-compose.images.yaml +++ b/docker-compose.images.yaml @@ -8,7 +8,7 @@ # CIO_VERSION=1.4.2 docker compose -f docker-compose.images.yaml --env-file .env up -d # # CIO_VERSION pins the release. Use a specific version (e.g. 1.4.2) in production; -# "latest" = newest stable release, "edge" = bleeding edge (last main build). +# "latest" = freshest main build (moves on every merge — rolling, may include unreleased code). # # KEEP IN SYNC with docker-compose.yaml: the api/dashboard/jobs `environment`, # healthchecks, depends_on, and volumes must match the build-based file. The only diff --git a/docker/docker-push.sh b/docker/docker-push.sh index 1da5d204..6502ffe2 100755 --- a/docker/docker-push.sh +++ b/docker/docker-push.sh @@ -8,16 +8,13 @@ set -e # Exit on error # Configuration DOCKERHUB_USERNAME="${DOCKERHUB_USERNAME:-classroomio}" # Change this to your Docker Hub username -VERSION="${VERSION:-latest}" # Can be overridden with VERSION env var +VERSION="${VERSION:?set VERSION to the tag to publish, e.g. VERSION=1.4.2 — this script never pushes :latest (CI owns latest from main)}" PUBLIC_IS_SELFHOSTED="${PUBLIC_IS_SELFHOSTED:-true}" PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}" -# Only a real semver release should also move the `latest` tag — building edge/main/dev -# must NOT clobber the stable `latest` pointer (mirrors docker-publish.yml's tag policy). -# Use `if` (not `&&`) so the function always returns 0 even when it emits no tag — a helper -# returning non-zero inside a $(...) argument is fragile under `set -e` / inherit_errexit. -is_release_version() { [[ "${VERSION}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; } -latest_arg() { if is_release_version; then printf -- '-t %s:latest' "$1"; fi; } +# This manual/local helper pushes ONLY the explicit ${VERSION} tag. `latest` is published +# exclusively by CI from `main` (see .github/workflows/docker-publish.yml) — to move `latest`, +# merge to main or re-run that workflow, never this script. # Colors for output GREEN='\033[0;32m' @@ -46,7 +43,6 @@ docker buildx build \ --platform "${PLATFORMS}" \ -f docker/Dockerfile.api \ -t ${DOCKERHUB_USERNAME}/api:${VERSION} \ - $(latest_arg ${DOCKERHUB_USERNAME}/api) \ --push \ . echo -e "${GREEN}✓ API image published successfully!${NC}" @@ -60,7 +56,6 @@ docker buildx build \ -f docker/Dockerfile.dashboard \ --build-arg PUBLIC_IS_SELFHOSTED=${PUBLIC_IS_SELFHOSTED} \ -t ${DOCKERHUB_USERNAME}/dashboard:${VERSION} \ - $(latest_arg ${DOCKERHUB_USERNAME}/dashboard) \ --push \ . echo -e "${GREEN}✓ Dashboard image published successfully!${NC}" @@ -72,7 +67,6 @@ docker buildx build \ --platform "${PLATFORMS}" \ -f docker/Dockerfile.jobs \ -t ${DOCKERHUB_USERNAME}/jobs:${VERSION} \ - $(latest_arg ${DOCKERHUB_USERNAME}/jobs) \ --push \ . echo -e "${GREEN}✓ Jobs image published successfully!${NC}" diff --git a/docker/docs/PUBLISHING_IMAGES.md b/docker/docs/PUBLISHING_IMAGES.md index a2459136..0d480281 100644 --- a/docker/docs/PUBLISHING_IMAGES.md +++ b/docker/docs/PUBLISHING_IMAGES.md @@ -57,8 +57,8 @@ Check your Docker Hub repository at: The workflow will automatically publish images when: -1. **Push to `main`**: Publishes the **`edge`** tag only (does NOT move `latest`). -2. **Push a version tag `vX.Y.Z`**: Publishes `X.Y.Z`, `X.Y`, `X`, and `latest`. +1. **Push to `main`**: Publishes the **`latest`** tag (the freshest `main` build). +2. **Push a version tag `vX.Y.Z`**: Publishes `X.Y.Z`, `X.Y`, `X` (immutable, pinned releases). 3. **Manual trigger**: Use the Actions tab in GitHub. Every build is **boot smoke-tested** (started against ephemeral postgres + redis, healthcheck @@ -107,7 +107,7 @@ docker pull classroomio/dashboard:latest For self-hosting with Docker Compose, see [SELF_HOST.md](SELF_HOST.md). The setup uses a single root `.env` file and `docker-compose.yaml`. -## Versioning Strategy (stable-releases + edge) +## Versioning Strategy (rolling `latest` + pinned releases) The published tags and what they mean: @@ -115,17 +115,15 @@ The published tags and what they mean: |-----|--------------|---------| | `1.4.2` (exact) | a `v1.4.2` git tag | Immutable, smoke-tested release — **what production should pin** | | `1.4`, `1` | a `v1.4.2` git tag | Latest patch/minor of that line | -| `latest` | a `v*` git tag only | The newest *released* version | -| `edge` | every push to `main` | Bleeding edge; may be unstable | +| `latest` | every push to `main` | The freshest `main` build (rolling; may include unreleased code) | -The key rule: **`latest` is never moved by a routine merge to `main`** — only a deliberate release -tag moves it. Self-hosters pin `CIO_VERSION` (see [SELF_HOST.md](SELF_HOST.md)) and upgrade on their -own schedule. +The key rule: **`latest` moves on every merge to `main`**, so it can change under you. Self-hosters +pin `CIO_VERSION` (see [SELF_HOST.md](SELF_HOST.md)) and upgrade on their own schedule. ### Cutting a release Versioning is driven by git tags; the CI workflow does the rest (build multi-arch → smoke-test → -push `X.Y.Z`/`X.Y`/`X`/`latest`). +push `X.Y.Z`/`X.Y`/`X`). `latest` is published separately, on every merge to `main`. ```bash # Bump version + create the vX.Y.Z tag from the repo's changelog tooling diff --git a/docker/docs/SELF_HOST.md b/docker/docs/SELF_HOST.md index 4928e127..4b7d8843 100644 --- a/docker/docs/SELF_HOST.md +++ b/docker/docs/SELF_HOST.md @@ -44,18 +44,16 @@ From a full clone you can also use the helper: `./run-docker-full-stack.sh --ima ## Versioning & Upgrades -Published images use a **stable-releases + edge** tag policy: +Published images use a **rolling `latest` + pinned releases** tag policy: | Tag | Meaning | Use it for | |-----|---------|-----------| | `1.4.2` (exact) | An immutable, smoke-tested release | **Production — always pin this** | | `1.4`, `1` | Latest patch/minor of that line | Tracking a line | -| `latest` | The newest *released* version | Trying it out | -| `edge` | The last `main` build (may be unstable) | Testing upcoming changes | +| `latest` | The freshest `main` build (rolling; may include unreleased code) | Trying the newest code | -`latest` only moves when a real release is tagged — a routine merge to `main` publishes `edge`, never -`latest`. **Pin `CIO_VERSION` to an exact version in production** so a new release can't break you -unattended. +`latest` moves on **every merge to `main`**, so it can change under you. **Pin `CIO_VERSION` to an +exact version in production** so a merge can't break you unattended. ```bash # Upgrade: bump the version, pull, recreate