Skip to content

Commit 3aa3dd3

Browse files
committed
feat: Implement CI/CD workflows for release automation
- Add build-images.yml to build and push Docker images after library publishing. - Create create-release.yml to tag and release new versions on GitHub. - Introduce deploy-prod.yml for deploying applications to production using Helm. - Add prepare-release.yml for preparing release PRs and version bumps. - Implement publish-chart.yml to package and publish Helm charts on release. - Add publish-libs-on-merge.yml to publish libraries to PyPI on main branch merges. - Update Tiltfile to support separate Dockerfiles for development and production. - Document migration plan for Dockerfile split in dev-prod-docker-split-plan.md. - Enhance release automation documentation in release-automation.md. - Update Helm templates to support image digest pinning and environment variable configuration. - Modify values.yaml to allow empty tags and optional digests for image pinning.
1 parent 0ebf3c7 commit 3aa3dd3

16 files changed

Lines changed: 841 additions & 39 deletions

.github/workflows/build-images.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: build-images
2+
on:
3+
workflow_run:
4+
workflows: [publish-libs-on-merge]
5+
types: [completed]
6+
7+
permissions:
8+
contents: read
9+
packages: write
10+
11+
jobs:
12+
build:
13+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Read version
19+
id: ver
20+
run: |
21+
echo "version=$(cat .version)" >> $GITHUB_OUTPUT
22+
23+
- name: Login to GHCR
24+
uses: docker/login-action@v3
25+
with:
26+
registry: ghcr.io
27+
username: ${{ github.actor }}
28+
password: ${{ secrets.GITHUB_TOKEN }}
29+
30+
- name: Set up Buildx
31+
uses: docker/setup-buildx-action@v3
32+
33+
- name: Build and push images
34+
run: |
35+
set -e
36+
VERSION="${{ steps.ver.outputs.version }}"
37+
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/rag-backend:${VERSION} -f services/rag-backend/Dockerfile .
38+
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/admin-backend:${VERSION} -f services/admin-backend/Dockerfile .
39+
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/document-extractor:${VERSION} -f services/document-extractor/Dockerfile .
40+
docker buildx build --push -t ghcr.io/stackitcloud/rag-template/mcp-server:${VERSION} -f services/mcp-server/Dockerfile .
41+
42+
- name: Capture image digests
43+
run: |
44+
VERSION="${{ steps.ver.outputs.version }}"
45+
: > image-digests.json
46+
for svc in rag-backend admin-backend document-extractor mcp-server; do
47+
ref="ghcr.io/stackitcloud/rag-template/${svc}:${VERSION}"
48+
digest=$(docker buildx imagetools inspect "$ref" --format '{{json .Manifest.Digest}}' | jq -r .)
49+
tmp=$(mktemp)
50+
jq --arg s "$svc" --arg t "$VERSION" --arg d "$digest" \
51+
'.[$s] = {"tag": $t, "digest": $d}' \
52+
image-digests.json > "$tmp"
53+
mv "$tmp" image-digests.json
54+
done
55+
56+
- name: Upload digests
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: image-digests
60+
path: image-digests.json
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: create-release
2+
on:
3+
workflow_run:
4+
workflows: [build-images]
5+
types: [completed]
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
release:
12+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Read version
18+
id: ver
19+
run: |
20+
echo "version=$(cat .version)" >> $GITHUB_OUTPUT
21+
22+
- name: Create Git tag
23+
run: |
24+
git config user.name "github-actions"
25+
git config user.email "github-actions@github.com"
26+
git tag -a "v${{ steps.ver.outputs.version }}" -m "Release v${{ steps.ver.outputs.version }}"
27+
git push origin "v${{ steps.ver.outputs.version }}"
28+
29+
- name: Create GitHub Release
30+
uses: softprops/action-gh-release@v2
31+
with:
32+
tag_name: v${{ steps.ver.outputs.version }}
33+
name: v${{ steps.ver.outputs.version }}
34+
generate_release_notes: true

.github/workflows/deploy-prod.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: deploy-prod
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "App version to deploy (e.g. 2.2.0)"
8+
required: true
9+
type: string
10+
release_name:
11+
description: "Helm release name"
12+
required: true
13+
default: "rag"
14+
type: string
15+
namespace:
16+
description: "Kubernetes namespace"
17+
required: true
18+
default: "rag"
19+
type: string
20+
chart_path:
21+
description: "Path to Helm chart"
22+
required: true
23+
default: "infrastructure/rag"
24+
type: string
25+
26+
permissions:
27+
contents: read
28+
packages: read
29+
30+
jobs:
31+
deploy:
32+
runs-on: ubuntu-latest
33+
environment: production
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- name: Setup Helm
38+
uses: azure/setup-helm@v4
39+
40+
- name: Set kubeconfig
41+
env:
42+
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
43+
run: |
44+
test -n "$KUBE_CONFIG" || { echo "KUBE_CONFIG secret is required"; exit 1; }
45+
mkdir -p "$HOME/.kube"
46+
echo "$KUBE_CONFIG" > "$HOME/.kube/config"
47+
chmod 600 "$HOME/.kube/config"
48+
49+
- name: Login to GHCR (for image inspect)
50+
uses: docker/login-action@v3
51+
with:
52+
registry: ghcr.io
53+
username: ${{ github.actor }}
54+
password: ${{ secrets.GITHUB_TOKEN }}
55+
56+
- name: Set up Buildx (for imagetools)
57+
uses: docker/setup-buildx-action@v3
58+
59+
- name: Resolve image digests
60+
id: digests
61+
env:
62+
VERSION: ${{ inputs.version }}
63+
run: |
64+
set -euo pipefail
65+
backend_ref="ghcr.io/stackitcloud/rag-template/rag-backend:${VERSION}"
66+
admin_ref="ghcr.io/stackitcloud/rag-template/admin-backend:${VERSION}"
67+
extractor_ref="ghcr.io/stackitcloud/rag-template/document-extractor:${VERSION}"
68+
mcp_ref="ghcr.io/stackitcloud/rag-template/mcp-server:${VERSION}"
69+
frontend_ref="ghcr.io/stackitcloud/rag-template/frontend:${VERSION}"
70+
71+
echo "backend=$(docker buildx imagetools inspect "$backend_ref" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT"
72+
echo "admin=$(docker buildx imagetools inspect "$admin_ref" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT"
73+
echo "extractor=$(docker buildx imagetools inspect "$extractor_ref" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT"
74+
echo "mcp=$(docker buildx imagetools inspect "$mcp_ref" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT"
75+
echo "frontend=$(docker buildx imagetools inspect "$frontend_ref" --format '{{.Manifest.Digest}}')" >> "$GITHUB_OUTPUT"
76+
77+
- name: Helm upgrade
78+
env:
79+
RELEASE: ${{ inputs.release_name }}
80+
NAMESPACE: ${{ inputs.namespace }}
81+
CHART_PATH: ${{ inputs.chart_path }}
82+
run: |
83+
set -euo pipefail
84+
helm upgrade --install "$RELEASE" "$CHART_PATH" \
85+
--namespace "$NAMESPACE" --create-namespace \
86+
--set backend.image.digest='${{ steps.digests.outputs.backend }}' \
87+
--set adminBackend.image.digest='${{ steps.digests.outputs.admin }}' \
88+
--set extractor.image.digest='${{ steps.digests.outputs.extractor }}' \
89+
--set backend.mcp.image.digest='${{ steps.digests.outputs.mcp }}' \
90+
--set frontend.image.digest='${{ steps.digests.outputs.frontend }}'
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: prepare-release
2+
on:
3+
workflow_dispatch: {}
4+
5+
permissions:
6+
contents: write
7+
pull-requests: write
8+
9+
concurrency:
10+
group: release
11+
cancel-in-progress: true
12+
13+
jobs:
14+
prepare:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Setup Node
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '20'
25+
26+
- name: Install semantic-release deps
27+
run: |
28+
npm ci
29+
30+
- name: Compute next version (dry-run)
31+
id: semrel
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
run: |
35+
npx semantic-release --dry-run --no-ci | tee semrel.log
36+
VERSION=$(grep -Eo "next release version is [0-9]+\.[0-9]+\.[0-9]+" semrel.log | awk '{print $5}')
37+
if [ -z "$VERSION" ]; then echo "No new release required"; exit 1; fi
38+
echo "$VERSION" > .version
39+
echo "version=$VERSION" >> $GITHUB_OUTPUT
40+
41+
- name: Bump libs and service pins
42+
run: |
43+
python -m pip install --upgrade pip
44+
pip install tomlkit
45+
python scripts/bump_pyproject_deps.py --version "${{ steps.semrel.outputs.version }}"
46+
47+
- name: Setup Python
48+
uses: actions/setup-python@v5
49+
with:
50+
python-version: '3.13'
51+
52+
- name: Install Poetry
53+
run: |
54+
pip install poetry==2.1.3
55+
56+
- name: Re-lock services
57+
run: |
58+
set -e
59+
for svc in services/rag-backend services/admin-backend services/document-extractor; do
60+
echo "Locking $svc"
61+
(cd "$svc" && poetry lock)
62+
done
63+
64+
- name: Update Helm chart versions
65+
run: |
66+
pip install pyyaml packaging
67+
python scripts/bump_chart_versions.py --app-version "${{ steps.semrel.outputs.version }}"
68+
69+
- name: Commit and open PR
70+
uses: peter-evans/create-pull-request@v6
71+
with:
72+
branch: chore/release-${{ steps.semrel.outputs.version }}
73+
title: "chore(release): prepare ${{ steps.semrel.outputs.version }}"
74+
body: |
75+
Prepare release ${{ steps.semrel.outputs.version }}
76+
- bump libs and service pins
77+
- update poetry.lock
78+
- update Helm chart appVersion and bump patch
79+
- add .version
80+
commit-message: "chore(release): prepare ${{ steps.semrel.outputs.version }}"
81+
add-paths: |
82+
.version
83+
libs/**/pyproject.toml
84+
services/**/pyproject.toml
85+
services/**/poetry.lock
86+
infrastructure/**/Chart.yaml
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: publish-chart
2+
on:
3+
release:
4+
types: [published]
5+
6+
permissions:
7+
contents: read
8+
packages: write
9+
10+
jobs:
11+
chart:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Setup Helm
17+
uses: azure/setup-helm@v4
18+
19+
- name: Login to GHCR for Helm OCI
20+
run: echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
21+
22+
- name: Package and push chart(s)
23+
run: |
24+
set -e
25+
for chart in infrastructure/*/Chart.yaml; do
26+
chart_dir=$(dirname "$chart")
27+
helm dependency update "$chart_dir" || true
28+
helm package "$chart_dir" --destination .
29+
done
30+
for tgz in *.tgz; do
31+
name=$(basename "$tgz" .tgz)
32+
helm push "$tgz" oci://ghcr.io/${{ github.repository_owner }}/charts
33+
done
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: publish-libs-on-merge
2+
on:
3+
push:
4+
branches: [main]
5+
paths:
6+
- '.version'
7+
- 'libs/**'
8+
- 'services/**/pyproject.toml'
9+
10+
permissions:
11+
contents: read
12+
packages: write
13+
14+
jobs:
15+
publish:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Read version
21+
id: ver
22+
run: |
23+
VERSION=$(cat .version)
24+
echo "version=$VERSION" >> $GITHUB_OUTPUT
25+
26+
- name: Setup Python
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: '3.13'
30+
31+
- name: Install Poetry
32+
run: |
33+
pip install poetry==2.1.3
34+
35+
- name: Build and publish libs to PyPI
36+
env:
37+
POETRY_HTTP_BASIC_PYPI_USERNAME: __token__
38+
POETRY_HTTP_BASIC_PYPI_PASSWORD: ${{ secrets.PYPI_TOKEN }}
39+
run: |
40+
set -e
41+
for lib in libs/*; do
42+
[ -d "$lib" ] || continue
43+
echo "Publishing $lib"
44+
(cd "$lib" && poetry version "${{ steps.ver.outputs.version }}" && poetry build && poetry publish)
45+
done

0 commit comments

Comments
 (0)