Skip to content

Commit 7fe7d3e

Browse files
authored
feat: add automated release pipeline with release-please (#58)
Replace the standalone Docker build workflow with a combined CI pipeline that uses release-please for automated version bumping, changelog generation, and GitHub releases. Docker images are built on native multi-platform runners (amd64 + arm64) and tagged with semantic versions on release, matching the pattern used in the cryptify repository. Closes #57
1 parent 37dcb0f commit 7fe7d3e

8 files changed

Lines changed: 163 additions & 83 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ coverage
1414
.nyc_output
1515

1616
# Production build
17+
build
1718
dist
1819

1920
# Misc

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VITE_APP_VERSION="1.0.0"
1+
VITE_APP_VERSION="1.0.0" # x-release-please-version
22
VITE_APP_NAME="PostGuard"
33
VITE_MAX_UPLOAD_SIZE=2147483648
44
VITE_UPLOAD_CHUNK_SIZE=1048576

.github/workflows/ci.yml

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: CI
2+
#
3+
# Pipeline overview
4+
# =================
5+
#
6+
# Release-please and Docker delivery are combined in one workflow because
7+
# GITHUB_TOKEN-created tags do not trigger new workflow runs. This means a
8+
# versioned Docker image must be built in the same run that release-please
9+
# creates the release, using its job outputs to pass the version through.
10+
#
11+
12+
on:
13+
push:
14+
branches:
15+
- main
16+
pull_request:
17+
18+
jobs:
19+
20+
# Create a GitHub release (and tag) when conventional commits warrant one.
21+
release-please:
22+
name: Release Please
23+
if: github.ref == 'refs/heads/main'
24+
runs-on: ubuntu-latest
25+
permissions:
26+
contents: write
27+
pull-requests: write
28+
outputs:
29+
release_created: ${{ steps.release.outputs.release_created }}
30+
version: ${{ steps.release.outputs.version }}
31+
steps:
32+
- uses: googleapis/release-please-action@v4
33+
id: release
34+
with:
35+
release-type: node
36+
37+
# Build each platform on its native runner and push by digest (no tag yet).
38+
build:
39+
name: Build (${{ matrix.name }})
40+
runs-on: ${{ matrix.runner }}
41+
permissions:
42+
contents: read
43+
packages: write
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
include:
48+
- platform: linux/amd64
49+
runner: ubuntu-24.04
50+
name: amd64
51+
- platform: linux/arm64
52+
runner: ubuntu-24.04-arm
53+
name: arm64
54+
steps:
55+
- name: Checkout repository
56+
uses: actions/checkout@v4
57+
- name: Set up Docker Buildx
58+
uses: docker/setup-buildx-action@v3
59+
- name: Log in to GHCR
60+
uses: docker/login-action@v3
61+
with:
62+
registry: ghcr.io
63+
username: ${{ github.actor }}
64+
password: ${{ secrets.GITHUB_TOKEN }}
65+
- name: Build and push by digest
66+
id: build
67+
uses: docker/build-push-action@v6
68+
with:
69+
context: .
70+
file: docker/Dockerfile
71+
platforms: ${{ matrix.platform }}
72+
outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
73+
cache-from: type=gha
74+
cache-to: type=gha,mode=max
75+
- name: Export digest
76+
run: |
77+
mkdir -p /tmp/digests
78+
digest="${{ steps.build.outputs.digest }}"
79+
touch "/tmp/digests/${digest#sha256:}"
80+
- name: Upload digest
81+
uses: actions/upload-artifact@v4
82+
with:
83+
name: digest-${{ matrix.name }}
84+
path: /tmp/digests/*
85+
if-no-files-found: error
86+
retention-days: 1
87+
88+
# Merge platform digests into a single multi-platform manifest and apply tags.
89+
# - push to main (no release) → ghcr.io/.../postguard-website:edge
90+
# - push to main (release) → ghcr.io/.../postguard-website:edge + :1.2.3
91+
# - pull request → ghcr.io/.../postguard-website:pr-123
92+
finalize:
93+
name: Finalize Docker manifest
94+
needs: [build, release-please]
95+
if: always() && needs.build.result == 'success'
96+
runs-on: ubuntu-latest
97+
permissions:
98+
contents: read
99+
packages: write
100+
steps:
101+
- name: Download digests
102+
uses: actions/download-artifact@v4
103+
with:
104+
path: /tmp/digests
105+
pattern: digest-*
106+
merge-multiple: true
107+
- name: Docker metadata
108+
id: meta
109+
uses: docker/metadata-action@v5
110+
with:
111+
images: ghcr.io/${{ github.repository }}
112+
tags: |
113+
type=edge,branch=main
114+
type=ref,event=pr
115+
type=raw,value=${{ needs.release-please.outputs.version }},enable=${{ needs.release-please.outputs.release_created == 'true' }}
116+
- name: Set up Docker Buildx
117+
uses: docker/setup-buildx-action@v3
118+
- name: Log in to GHCR
119+
uses: docker/login-action@v3
120+
with:
121+
registry: ghcr.io
122+
username: ${{ github.actor }}
123+
password: ${{ secrets.GITHUB_TOKEN }}
124+
- name: Create and push manifest
125+
working-directory: /tmp/digests
126+
run: |
127+
docker buildx imagetools create \
128+
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
129+
$(printf 'ghcr.io/${{ github.repository }}@sha256:%s ' *)

.github/workflows/docker-build.yml

Lines changed: 0 additions & 75 deletions
This file was deleted.

.release-please-manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "1.0.0"
3+
}

docker/Dockerfile

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
# Compiles ngx_brotli from source as a dynamic module, then copies the
2-
# .so files into the official nginx image (which keeps envsubst template
3-
# support and the standard entrypoint).
1+
# Multi-stage build: compile SvelteKit frontend, build Brotli modules,
2+
# then assemble the final Nginx image with envsubst template support.
43

54
ARG NGINX_VERSION=1.27.4
65

76
# ============================================================
8-
# Stage 1 — Build brotli dynamic modules
7+
# Stage 1 — Build SvelteKit frontend
8+
# ============================================================
9+
FROM node:20-slim AS frontend
10+
11+
WORKDIR /app
12+
COPY package.json yarn.lock .env ./
13+
RUN corepack enable && yarn install --frozen-lockfile
14+
COPY src/ src/
15+
COPY static/ static/
16+
COPY svelte.config.js vite.config.ts tsconfig.json ./
17+
RUN npx svelte-kit sync && yarn build
18+
19+
# ============================================================
20+
# Stage 2 — Build brotli dynamic modules
921
# ============================================================
1022
FROM debian:bookworm-slim AS builder
1123

@@ -62,7 +74,7 @@ COPY docker/default.conf.template /etc/nginx/templates/default.conf.template
6274
COPY docker/entrypoint.sh /docker-entrypoint.d/40-substitute-env-vars.sh
6375
RUN chmod +x /docker-entrypoint.d/40-substitute-env-vars.sh
6476

65-
# Copy pre-built frontend
66-
COPY build /usr/share/nginx/html/postguard
77+
# Copy frontend built in Stage 1
78+
COPY --from=frontend /app/build /usr/share/nginx/html/postguard
6779

6880
EXPOSE 80

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "postguard-website",
3-
"version": "0.0.1",
3+
"version": "1.0.0",
44
"private": true,
55
"scripts": {
66
"dev": "vite dev",

release-please-config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3+
"packages": {
4+
".": {
5+
"release-type": "node",
6+
"changelog-path": "CHANGELOG.md",
7+
"extra-files": [".env"]
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)