Skip to content

Commit 0abd991

Browse files
authored
fix(ci-cd): split release workflows (#1752)
1 parent 20ffe92 commit 0abd991

6 files changed

Lines changed: 176 additions & 96 deletions

File tree

.github/workflows/bump-and-tag.yml

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Deploy staging
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
tag:
7+
description: 'Release tag being deployed, such as v1.2.3'
8+
required: true
9+
type: string
10+
workflow_dispatch:
11+
inputs:
12+
tag:
13+
description: 'Release tag being deployed, such as v1.2.3'
14+
required: true
15+
type: string
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
deploy:
22+
name: Deploy staging
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Deploy release to staging
27+
env:
28+
RELEASE_TAG: ${{ inputs.tag }}
29+
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
30+
SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
31+
SSH_USER: ${{ secrets.STAGING_SSH_USER }}
32+
run: |
33+
echo "Deploying Compass ${RELEASE_TAG} to staging"
34+
mkdir -p ~/.ssh
35+
echo "$SSH_KEY" > ~/.ssh/staging_key
36+
chmod 600 ~/.ssh/staging_key
37+
ssh-keyscan -H "$SSH_HOST" >> ~/.ssh/known_hosts
38+
ssh -i ~/.ssh/staging_key "$SSH_USER@$SSH_HOST" "cd ~/compass && ./compass update"

.github/workflows/publish-images.yml renamed to .github/workflows/publish-docker-images.yml

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ on:
77
workflow_call:
88
inputs:
99
tag:
10-
description: 'Semver tag to build (e.g. v1.2.3)'
10+
description: 'Semver tag to build, such as v1.2.3'
11+
required: true
12+
type: string
13+
workflow_dispatch:
14+
inputs:
15+
tag:
16+
description: 'Semver tag to build, such as v1.2.3'
1117
required: true
1218
type: string
1319

@@ -16,22 +22,29 @@ permissions:
1622

1723
jobs:
1824
publish:
25+
name: Publish Docker images
1926
runs-on: ubuntu-latest
2027

2128
steps:
22-
- name: Check out repository
29+
- name: Check out release tag
2330
uses: actions/checkout@v6
2431
with:
2532
ref: ${{ inputs.tag || github.ref }}
2633

27-
- name: Derive semver tags from git tag
34+
- name: Derive Docker image tags
2835
id: version
2936
run: |
30-
TAG="${{ inputs.tag || github.ref_name }}" # e.g. v1.2.3
31-
VERSION="${TAG#v}" # 1.2.3
32-
MINOR="${VERSION%.*}" # 1.2
33-
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
34-
echo "minor=${MINOR}" >> "$GITHUB_OUTPUT"
37+
release_tag="${{ inputs.tag || github.ref_name }}"
38+
image_version="${release_tag#v}"
39+
minor_tag="${image_version%.*}"
40+
41+
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
42+
echo "image_version=${image_version}" >> "$GITHUB_OUTPUT"
43+
echo "minor_tag=${minor_tag}" >> "$GITHUB_OUTPUT"
44+
45+
echo "Release tag: ${release_tag}"
46+
echo "Image version: ${image_version}"
47+
echo "Minor tag: ${minor_tag}"
3548
3649
- name: Log in to Docker Hub
3750
uses: docker/login-action@v3
@@ -49,8 +62,8 @@ jobs:
4962
file: self-host/Dockerfile.backend
5063
push: true
5164
tags: |
52-
switchbacktech/compass-backend:${{ steps.version.outputs.version }}
53-
switchbacktech/compass-backend:${{ steps.version.outputs.minor }}
65+
switchbacktech/compass-backend:${{ steps.version.outputs.image_version }}
66+
switchbacktech/compass-backend:${{ steps.version.outputs.minor_tag }}
5467
switchbacktech/compass-backend:latest
5568
5669
- name: Build and push compass-mongo
@@ -60,8 +73,8 @@ jobs:
6073
file: self-host/Dockerfile.mongo
6174
push: true
6275
tags: |
63-
switchbacktech/compass-mongo:${{ steps.version.outputs.version }}
64-
switchbacktech/compass-mongo:${{ steps.version.outputs.minor }}
76+
switchbacktech/compass-mongo:${{ steps.version.outputs.image_version }}
77+
switchbacktech/compass-mongo:${{ steps.version.outputs.minor_tag }}
6578
switchbacktech/compass-mongo:latest
6679
6780
- name: Build and push compass-web
@@ -77,25 +90,8 @@ jobs:
7790
build-args: |
7891
BASEURL=http://localhost:3000/api
7992
GOOGLE_CLIENT_ID=compass-self-host-placeholder.apps.googleusercontent.com
80-
COMPASS_BUILD_REF=${{ steps.version.outputs.version }}
93+
COMPASS_BUILD_REF=${{ steps.version.outputs.image_version }}
8194
tags: |
82-
switchbacktech/compass-web:${{ steps.version.outputs.version }}
83-
switchbacktech/compass-web:${{ steps.version.outputs.minor }}
95+
switchbacktech/compass-web:${{ steps.version.outputs.image_version }}
96+
switchbacktech/compass-web:${{ steps.version.outputs.minor_tag }}
8497
switchbacktech/compass-web:latest
85-
86-
deploy-staging:
87-
needs: publish
88-
runs-on: ubuntu-latest
89-
90-
steps:
91-
- name: Deploy to staging
92-
env:
93-
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
94-
SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
95-
SSH_USER: ${{ secrets.STAGING_SSH_USER }}
96-
run: |
97-
mkdir -p ~/.ssh
98-
echo "$SSH_KEY" > ~/.ssh/staging_key
99-
chmod 600 ~/.ssh/staging_key
100-
ssh-keyscan -H "$SSH_HOST" >> ~/.ssh/known_hosts
101-
ssh -i ~/.ssh/staging_key "$SSH_USER@$SSH_HOST" "cd ~/compass && ./compass update"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Release on main
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
tag-release:
13+
name: Tag release
14+
runs-on: ubuntu-latest
15+
outputs:
16+
release_tag: ${{ steps.tag.outputs.release_tag }}
17+
18+
steps:
19+
- name: Check out repository
20+
uses: actions/checkout@v6
21+
with:
22+
fetch-depth: 0
23+
24+
- name: Bump patch version and push tag
25+
id: tag
26+
run: |
27+
set -euo pipefail
28+
29+
max_attempts=5
30+
31+
for attempt in $(seq 1 "$max_attempts"); do
32+
git fetch --force --tags origin
33+
34+
latest=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)
35+
[ -z "$latest" ] && latest="v0.0.0"
36+
37+
version="${latest#v}"
38+
major="${version%%.*}"
39+
rest="${version#*.}"
40+
minor="${rest%%.*}"
41+
patch="${rest#*.}"
42+
release_tag="v${major}.${minor}.$((patch + 1))"
43+
44+
echo "Attempt ${attempt}/${max_attempts}: bumping ${latest} -> ${release_tag}"
45+
46+
if git tag "$release_tag" && git push origin "$release_tag"; then
47+
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
48+
exit 0
49+
fi
50+
51+
git tag -d "$release_tag" >/dev/null 2>&1 || true
52+
sleep "$attempt"
53+
done
54+
55+
echo "Could not push a unique release tag after ${max_attempts} attempts." >&2
56+
exit 1
57+
58+
publish-docker-images:
59+
name: Publish Docker images
60+
needs: tag-release
61+
uses: ./.github/workflows/publish-docker-images.yml
62+
with:
63+
tag: ${{ needs.tag-release.outputs.release_tag }}
64+
secrets: inherit
65+
66+
deploy-staging:
67+
name: Deploy staging
68+
needs:
69+
- tag-release
70+
- publish-docker-images
71+
uses: ./.github/workflows/deploy-staging.yml
72+
with:
73+
tag: ${{ needs.tag-release.outputs.release_tag }}
74+
secrets: inherit

docs/CI-CD/versioning.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Compass uses two complementary version identifiers.
66

77
Format: `v{MAJOR}.{MINOR}.{PATCH}` — e.g. `v0.5.4`
88

9-
- Bumped automatically on every merge to `main` (patch) by `bump-and-tag.yml`
9+
- Bumped automatically on every merge to `main` (patch) by `release-on-main.yml`
1010
- Minor/major bumps are manual: push a tag like `v0.6.0` directly
1111
- Used to tag Docker images on Docker Hub (`switchbacktech/compass-web:0.5.4`, `:0.5`, `:latest`)
1212

@@ -22,10 +22,12 @@ A string baked into the web bundle at build time by `packages/web/build.ts` and
2222

2323
### How it flows in CI
2424

25-
1. `bump-and-tag.yml` pushes `v0.5.4`
26-
2. `publish-images.yml` fires, sets `COMPASS_BUILD_REF=0.5.4` as a Docker build arg
27-
3. `build.ts` reads `COMPASS_BUILD_REF`, sets `BUILD_VERSION = "0.5.4"`
28-
4. Bundle and `version.json` both contain `"0.5.4"`
25+
1. `release-on-main.yml` pushes `v0.5.4` from its `tag-release` job.
26+
2. `release-on-main.yml` calls `publish-docker-images.yml` with `tag=v0.5.4`.
27+
3. `publish-docker-images.yml` sets `COMPASS_BUILD_REF=0.5.4` as a Docker build arg.
28+
4. `build.ts` reads `COMPASS_BUILD_REF`, sets `BUILD_VERSION = "0.5.4"`.
29+
5. Bundle and `version.json` both contain `"0.5.4"`.
30+
6. `release-on-main.yml` calls `deploy-staging.yml` after Docker publishing succeeds.
2931

3032
### Adding a new build context
3133

docs/CI-CD/workflows.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ Compass uses GitHub Actions for continuous integration, Docker Hub for image dis
66
|---|---|---|
77
| Test | Push / PR to `main` | Runs lint, type-check, and unit tests |
88
| CodeQL | Push / PR to `main` | Static security analysis |
9-
| Bump version and tag | Push to `main` | Auto-increments patch version, pushes a new semver tag |
10-
| Publish Docker images | Push a `v*.*.*` tag | Builds images, pushes to Docker Hub, deploys to staging |
9+
| Release on main | Push to `main` | Auto-increments patch version, publishes Docker images, then deploys staging |
10+
| Publish Docker images | Reusable workflow / manual dispatch / manual `v*.*.*` tag push | Builds and pushes Docker images only |
11+
| Deploy staging | Reusable workflow / manual dispatch | Pulls published images on staging and restarts the stack |
1112
| Sync docs to compass-docs | Push to `main` touching `docs/**` | Mirrors this `docs/` directory to docs.compasscalendar.com |
1213

1314
---
@@ -18,12 +19,20 @@ Every PR merge to `main` triggers a fully automated chain:
1819

1920
```
2021
PR merged to main
21-
└─► bump-and-tag.yml — reads latest tag, pushes v1.2.X+1
22-
└─► publish-images.yml — builds & pushes images to Docker Hub
23-
└─► deploy-staging — SSHes into VPS, runs ./compass update
22+
└─► release-on-main.yml
23+
├─► tag-release — reads latest tag, pushes v1.2.X+1
24+
├─► publish-docker-images — builds and pushes Docker Hub images
25+
└─► deploy-staging — SSHes into VPS, runs ./compass update
2426
```
2527

26-
**Monthly minor/major releases** remain manual: a maintainer pushes a tag like `v1.3.0` or `v2.0.0`, which skips the bump step and goes straight to publish + deploy.
28+
The automatic path calls reusable workflows directly. It uses `GITHUB_TOKEN` to
29+
push the git tag, then passes that tag to the publish and deploy workflows. It
30+
does not rely on the workflow-created tag push to trigger another workflow.
31+
32+
**Monthly minor/major releases** remain manual: a maintainer pushes a tag like
33+
`v1.3.0` or `v2.0.0`, which skips the bump step and runs
34+
`Publish Docker images`. Staging deploys for manual tags are explicit: run
35+
`Deploy staging` with the existing tag after the images are published.
2736

2837
### Removing a test tag
2938

@@ -36,11 +45,12 @@ git tag -d v1.2.3
3645

3746
## Publish Docker Images
3847

39-
Source: [`.github/workflows/publish-images.yml`](../../.github/workflows/publish-images.yml)
48+
Source: [`.github/workflows/publish-docker-images.yml`](../../.github/workflows/publish-docker-images.yml)
4049

4150
### How it works
4251

43-
1. A semver tag matching `v[0-9]+.[0-9]+.[0-9]+` is pushed (either by `bump-and-tag.yml` or manually).
52+
1. A semver tag is provided by `release-on-main.yml`, by manual workflow dispatch,
53+
or by a manually pushed tag matching `v[0-9]+.[0-9]+.[0-9]+`.
4454
2. The workflow strips the `v` prefix and derives two tag aliases:
4555
- `1.2.3` — exact patch version
4656
- `1.2` — floating minor alias
@@ -49,27 +59,33 @@ Source: [`.github/workflows/publish-images.yml`](../../.github/workflows/publish
4959
- `switchbacktech/compass-mongo`
5060
- `switchbacktech/compass-web`
5161
4. Each image gets all three tags: `1.2.3`, `1.2`, and `latest`.
52-
5. After all images are pushed, the `deploy-staging` job runs.
5362

5463
### Tag pattern rules
5564

56-
Only clean semver tags trigger the workflow. Tags with suffixes (e.g. `v1.2.3-test`) do not match and are safe to push for local testing without triggering a deploy.
65+
Only clean semver tags trigger this workflow from a tag push. Tags with suffixes
66+
(e.g. `v1.2.3-test`) do not match and are safe to push for local testing without
67+
publishing images.
5768

5869
---
5970

6071
## Staging Deploy
6172

62-
Source: `deploy-staging` job in [`.github/workflows/publish-images.yml`](../../.github/workflows/publish-images.yml)
73+
Source: [`.github/workflows/deploy-staging.yml`](../../.github/workflows/deploy-staging.yml)
74+
75+
The deploy workflow SSHes into the staging VPS and runs `./compass update`,
76+
which pulls the Docker Hub image tag configured by the staging `.env` file and
77+
restarts the stack. The workflow accepts a release tag input so the Actions logs
78+
show which release triggered or motivated the deploy.
6379

64-
The deploy job SSHes into the staging VPS and runs `./compass update`, which pulls the latest Docker Hub images and restarts the stack.
80+
Manual staging redeploys do not rebuild images. Run `Deploy staging` with an
81+
existing tag after confirming the desired image tags already exist on Docker Hub.
6582

6683
### Required secrets
6784

6885
All secrets go in **GitHub → Settings → Secrets and variables → Actions**:
6986

7087
| Secret | Value |
7188
|---|---|
72-
| `COMPASS_CI_TOKEN` | Fine-grained PAT needed for the bump and tag workflow |
7389
| `DOCKERHUB_USERNAME` | Docker Hub username for the `switchbacktech` org |
7490
| `DOCKERHUB_TOKEN` | Docker Hub personal access token (Read & Write) |
7591
| `STAGING_SSH_HOST` | VPS IP address or hostname |

0 commit comments

Comments
 (0)