chore(release): use next as the development branch

Sync main into next automatically and update release workflows and contributor docs for the long-lived next branch.
This commit is contained in:
Andras Bacsai committed 2026-10-20 18:44:26 +02:00
1 parent 87dade4517
commit 408ee23936
6 files changed
+89 -251

No files matched your search

@@ -1,144 +0,0 @@
name: Build Coolify Release Branch
on:
push:
branches: ["release/v*"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- .github/workflows/coolify-helper-next.yml
- .github/workflows/pr-quality.yaml
- docker/coolify-helper/Dockerfile
- docker/testing-host/Dockerfile
- templates/**
- CHANGELOG.md
permissions:
contents: read
packages: write
concurrency:
group: coolify-release-branch-build-${{ github.ref_name }}
cancel-in-progress: false
env:
GITHUB_REGISTRY: ghcr.io
DOCKER_REGISTRY: docker.io
IMAGE_NAME: coollabsio/coolify
jobs:
prepare:
runs-on: ubuntu-24.04
outputs:
dev_version: ${{ steps.version.outputs.dev_version }}
short_sha: ${{ steps.version.outputs.short_sha }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Resolve version from branch name
id: version
env:
BRANCH: ${{ github.ref_name }}
run: |
if [[ ! "${BRANCH}" =~ ^release/v([0-9]+)\.([0-9]+)$ ]]; then
echo "Invalid release branch: ${BRANCH}. Expected release/vX.Y, such as release/v4.5."
exit 1
fi
DEV_VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}-dev"
SHORT_SHA="${GITHUB_SHA::7}"
echo "dev_version=${DEV_VERSION}" >> "$GITHUB_OUTPUT"
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "version=${DEV_VERSION}.${SHORT_SHA}" >> "$GITHUB_OUTPUT"
build:
needs: prepare
strategy:
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: aarch64
platform: linux/aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push release branch image (${{ matrix.arch }})
uses: docker/build-push-action@v6
with:
context: .
file: docker/production/Dockerfile
platforms: ${{ matrix.platform }}
push: true
build-args: |
COOLIFY_VERSION=${{ needs.prepare.outputs.version }}
tags: |
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
publish:
needs: [prepare, build]
runs-on: ubuntu-24.04
strategy:
matrix:
registry: [ghcr.io, docker.io]
steps:
- uses: docker/setup-buildx-action@v3
- name: Login to ${{ env.GITHUB_REGISTRY }}
if: matrix.registry == 'ghcr.io'
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to ${{ env.DOCKER_REGISTRY }}
if: matrix.registry == 'docker.io'
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Publish release branch manifest on ${{ matrix.registry }}
env:
REGISTRY: ${{ matrix.registry }}
DEV_VERSION: ${{ needs.prepare.outputs.dev_version }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
IMAGE="${REGISTRY}/${IMAGE_NAME}"
docker buildx imagetools create \
"${IMAGE}:${VERSION}-amd64" \
"${IMAGE}:${VERSION}-aarch64" \
--tag "${IMAGE}:${VERSION}" \
--tag "${IMAGE}:${DEV_VERSION}"
notify:
needs: [prepare, build, publish]
if: always()
runs-on: ubuntu-24.04
steps:
- uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
status: ${{ contains(needs.*.result, 'failure') && 'failure' || (contains(needs.*.result, 'cancelled') && 'cancelled' || 'success') }}
+65
View File
@@ -0,0 +1,65 @@
name: Sync main to next
on:
schedule:
- cron: '*/10 * * * *'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: sync-main-to-next
cancel-in-progress: false
jobs:
sync:
name: Merge main into next
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Merge main into next
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git fetch origin main next
git checkout --quiet -B next origin/next
if git merge --no-edit origin/main; then
git push origin HEAD:refs/heads/next
exit 0
fi
conflicts=$(git diff --name-only --diff-filter=U)
git merge --abort
if [ -z "$conflicts" ]; then
echo '::error title=main to next sync failed::The merge failed without conflicts, so no pull request was created.'
exit 1
fi
sync_branch='automation/sync-main-to-next'
existing_pr=$(gh pr list --base next --head "$sync_branch" --state open --json url --jq '.[0].url')
if [ -n "$existing_pr" ]; then
# The conflict was already reported when this pull request was opened.
# Stay green until it is merged instead of failing every scheduled run.
echo "::notice title=main to next sync blocked::main still conflicts with next; resolve the open pull request: $existing_pr"
exit 0
fi
git push --force origin origin/main:"refs/heads/$sync_branch"
gh pr create \
--base next \
--head "$sync_branch" \
--title 'chore: merge main into next' \
--body 'This pull request was created automatically because main could not be merged into next without conflicts. Resolve conflicts on this temporary branch; never update main with next.'
echo '::error title=main to next sync blocked::main could not be merged into next without conflicts; a pull request was opened to resolve them.'
exit 1
@@ -1,81 +0,0 @@
name: Sync main to release branches
on:
schedule:
- cron: '*/10 * * * *'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: sync-main-to-release
cancel-in-progress: false
jobs:
sync:
name: Merge main into release branches
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Merge main into every release/v* branch
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git fetch origin main '+refs/heads/release/v*:refs/remotes/origin/release/v*'
branches=$(git for-each-ref --format='%(refname:lstrip=3)' 'refs/remotes/origin/release/v*')
if [ -z "$branches" ]; then
echo 'No release/v* branch exists, so there is nothing to sync.'
exit 0
fi
failed=0
for branch in $branches; do
echo "::group::Sync main into $branch"
git checkout --quiet -B "$branch" "origin/$branch"
if git merge --no-edit origin/main; then
git push origin "HEAD:refs/heads/$branch"
echo '::endgroup::'
continue
fi
conflicts=$(git diff --name-only --diff-filter=U)
git merge --abort
echo '::endgroup::'
if [ -z "$conflicts" ]; then
echo "::error title=main to $branch sync failed::The merge failed without conflicts, so no pull request was created."
failed=1
continue
fi
sync_branch="automation/sync-main-to-${branch//\//-}"
existing_pr=$(gh pr list --base "$branch" --head "$sync_branch" --state open --json url --jq '.[0].url')
if [ -n "$existing_pr" ]; then
# The conflict was already reported when this pull request was opened.
# Stay green until it is merged instead of failing every scheduled run.
echo "::notice title=main to $branch sync blocked::main still conflicts with $branch; resolve the open pull request: $existing_pr"
continue
fi
git push --force origin origin/main:"refs/heads/$sync_branch"
gh pr create \
--base "$branch" \
--head "$sync_branch" \
--title "chore: merge main into $branch" \
--body "This pull request was created automatically because main could not be merged into $branch without conflicts. Resolve conflicts on this temporary branch; never update main with $branch."
echo "::error title=main to $branch sync blocked::main could not be merged into $branch without conflicts; a pull request was opened to resolve them."
failed=1
done
exit "$failed"
+2 -2
View File
@@ -251,8 +251,8 @@ The persistent-storage implementation is the reference pattern: `Project\Service
## Git Workflow
- Production branch: `main`
- Development branch: `release/v4.x`, where `x` is the next minor release (for example, `release/v4.5` while production is `4.4.x`). Do not use `next`.
- Fix PRs should target the current production branch; feature PRs should target the current `release/v4.x` branch
- Development branch: `next`. Do not create `release/v*` branches.
- Fix PRs should target the current production branch; feature PRs should target `next`
<laravel-boost-guidelines>
=== foundation rules ===
+3 -4
View File
@@ -193,12 +193,11 @@ Choose the branch based on the type of change:
| --- | --- | --- |
| Fixes and small improvements | `main` | `main` |
| Security fixes | `main` | `main` |
| New features and larger changes | `release/v4.x` | `release/v4.x` |
| New features and larger changes | `next` | `next` |
- For a fix, branch from `main` and target `main`.
- For a feature, branch from `release/v4.x` and target `release/v4.x`.
- `x` is the number of the next minor release. For example, when the production version is `4.4.x`, use `release/v4.5`. There is no `next` branch.
- If a fix is discovered while developing a feature, submit it separately to `main`. Maintainers will merge `main` into `release/v4.x` so the fix is included there too.
- For a feature, branch from `next` and target `next`.
- If a fix is discovered while developing a feature, submit it separately to `main`. `main` is merged into `next` automatically, so the fix is included there too.
- Pull requests targeting the wrong branch may be closed or asked to retarget.
+19 -20
View File
@@ -5,54 +5,54 @@
| Branch | Purpose |
| --- | --- |
| `main` | Latest production source |
| `release/v4.x` | Feature integration and RC releases for the next minor release, such as `release/v4.5` |
| `feature/*` | New features based on and merged into the current `release/v4.x` branch |
| `next` | Feature integration and RC releases for the next version |
| `feature/*` | New features based on and merged into `next` |
| `hotfix/X.Y.Z` | Production fixes based on `main` |
`x` is the number of the next minor release. When `4.4.x` is the production version, the release branch is `release/v4.5`. Create a new `release/v4.x` branch from `main` after each stable minor release. Do not use a long-lived `next` branch.
`next` is the only long-lived development branch. Do not create `release/v*` branches.
Release workflows never edit or commit versions. Stable versions come from `config/constants.php`; RC versions come from `coolify.nightly.version` in `versions.json` and `other/nightly/versions.json`.
## Where changes go
- Fixes, security updates, and small improvements target `main`.
- New features and larger changes target the current `release/v4.x` branch.
- **Sync main to release branches** merges `main` into every `release/v*` branch every 10 minutes, so every production fix is included in the next release. If a merge conflicts, it opens a `chore: merge main into release/vX.Y` pull request to resolve.
- Do not merge `release/v4.x` into `main` until an RC is approved for a stable release.
- New features and larger changes target `next`.
- **Sync main to next** merges `main` into `next` every 10 minutes, so every production fix is included in the next release. If the merge conflicts, it opens a `chore: merge main into next` pull request to resolve.
- Do not merge `next` into `main` until an RC is approved for a stable release.
## Feature and RC flow
```text
feature/* → release/v4.x → RC
feature/* → next → RC
```
1. Merge feature branches into `release/v4.x`.
1. Merge feature branches into `next`.
2. Set `coolify.nightly.version` in both version files to the intended RC, such as `4.5-rc.1`.
3. Every push to `release/v4.x` runs **Build Coolify Release Branch**. It calculates the version from the branch name and publishes `4.5-dev.<short-sha>` and the moving `4.5-dev` tag. It never publishes an RC or stable tag.
3. Every push to `next` runs **Build Coolify Next**. It publishes `sha-<short-sha>`, `4.5-rc.1.<short-sha>`, and the moving `next` tag. It never publishes the exact `4.5-rc.1` tag.
4. Create a reviewed draft GitHub Release named `v4.5-rc.1` and mark it as a prerelease.
5. Run **Release Coolify RC** manually from `release/v4.x` and enter `v4.5-rc.1`.
5. Run **Release Coolify RC** manually from `next` and enter `v4.5-rc.1`.
6. The workflow validates the draft and configured nightly version, builds the exact RC, publishes `4.5-rc.1`, updates the `next` image tag, and publishes the draft prerelease.
7. Advance `coolify.nightly.version` to the next intended RC version.
## Stable release flow
```text
release/v4.x → main → stable release
next → main → stable release
```
1. Temporarily stop merging features into `release/v4.x`.
2. Change the version on `release/v4.x` from the approved RC to the stable version, such as `4.5.0`.
3. Merge `release/v4.x` into `main`.
1. Temporarily stop merging features into `next`.
2. Change the version on `next` from the approved RC to the stable version, such as `4.5.0`.
3. Merge `next` into `main`.
4. Create a reviewed draft GitHub Release named `v4.5.0`.
5. Run the stable release workflow from `main`.
6. The workflow rebuilds the exact stable version, publishes `4.5.0` and `latest`, then publishes the draft.
7. Update the CDN only after the release is approved.
8. Create the next release branch from `main`, such as `release/v4.6`, and set its development version. Delete the old release branch.
8. Advance `next` to the next development version, such as `4.6-rc.1`.
## Hotfix flow
```text
main → hotfix/X.Y.Z → main → release/v4.x
main → hotfix/X.Y.Z → main → next
```
1. Create `hotfix/X.Y.Z` from `main` when a patch needs an integration branch. A single fix may use a normal branch from `main` instead.
@@ -61,7 +61,7 @@ main → hotfix/X.Y.Z → main → release/v4.x
4. Merge the fix into `main`.
5. Create a reviewed draft GitHub Release named `vX.Y.Z`.
6. Run the stable release workflow from `main`.
7. Merge `main` into `release/v4.x`, resolve the version in favor of the next intended RC, and delete the hotfix branch if one was used.
7. **Sync main to next** merges `main` into `next`. If the version conflicts, resolve it in favor of the next intended RC in the sync pull request. Delete the hotfix branch if one was used.
8. Update the CDN only after the release is approved.
## Image tags
@@ -69,9 +69,8 @@ main → hotfix/X.Y.Z → main → release/v4.x
| Tag | Meaning |
| --- | --- |
| `latest` | Latest stable release |
| `next` | Latest successful `next` build (legacy branch) |
| `X.Y-dev` | Latest successful `release/vX.Y` build |
| `X.Y-dev.<short-sha>` | Exact `release/vX.Y` commit build |
| `next` | Latest successful `next` build or RC release |
| `X.Y-rc.N.<short-sha>` | Exact `next` commit build |
| `X.Y.Z` | Exact stable release |
| `X.Y-rc.N` | Exact RC release |
| `sha-<commit>` | Exact commit build |