Skip to content

Commit 65e81ba

Browse files
authored
Merge pull request #6 from ethos-link/allow-revisions
Support immutable multi-arch image releases
2 parents ae1877c + 647d2d4 commit 65e81ba

5 files changed

Lines changed: 201 additions & 67 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ on:
88
workflow_dispatch:
99
inputs:
1010
postgres_version:
11-
description: 'PostgreSQL version to build'
12-
required: true
13-
default: '18.1'
11+
description: 'PostgreSQL base image version to build from'
12+
required: false
13+
revision:
14+
description: 'Image rebuild revision, e.g. 1 publishes <postgres_version>-r1'
15+
required: false
1416

1517
env:
1618
REGISTRY_DOCKER: docker.io
@@ -50,9 +52,49 @@ jobs:
5052
username: ${{ github.actor }}
5153
password: ${{ secrets.GITHUB_TOKEN }}
5254

53-
- name: Read VERSION
55+
- name: Read versions
5456
id: version
55-
run: echo "VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT
57+
run: |
58+
POSTGRES_VERSION="${{ github.event.inputs.postgres_version }}"
59+
REVISION="${{ github.event.inputs.revision }}"
60+
61+
if [ -z "$POSTGRES_VERSION" ]; then
62+
POSTGRES_VERSION="$(cat POSTGRES_VERSION)"
63+
fi
64+
65+
if [ -n "$REVISION" ]; then
66+
if ! [[ "$REVISION" =~ ^[1-9][0-9]*$ ]]; then
67+
echo "Revision must be a positive integer." >&2
68+
exit 1
69+
fi
70+
71+
IMAGE_VERSION="$POSTGRES_VERSION-r$REVISION"
72+
else
73+
IMAGE_VERSION="$(cat VERSION)"
74+
fi
75+
76+
echo "POSTGRES_VERSION=$POSTGRES_VERSION" >> "$GITHUB_OUTPUT"
77+
echo "IMAGE_VERSION=$IMAGE_VERSION" >> "$GITHUB_OUTPUT"
78+
79+
- name: Refuse existing release tag
80+
if: github.event_name != 'pull_request'
81+
run: |
82+
IMAGE_VERSION="${{ steps.version.outputs.IMAGE_VERSION }}"
83+
84+
for image in \
85+
"${{ env.REGISTRY_DOCKER }}/ethoslink/postgres-backrest" \
86+
"${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }}"
87+
do
88+
if docker buildx imagetools inspect "$image:$IMAGE_VERSION" >/dev/null 2>&1; then
89+
echo "Refusing to overwrite existing image tag: $image:$IMAGE_VERSION" >&2
90+
exit 1
91+
fi
92+
done
93+
94+
if git ls-remote --exit-code --tags origin "refs/tags/$IMAGE_VERSION" >/dev/null 2>&1; then
95+
echo "Refusing to recreate existing git tag: $IMAGE_VERSION" >&2
96+
exit 1
97+
fi
5698
5799
- name: Extract metadata (tags, labels) for Docker
58100
id: meta
@@ -62,15 +104,13 @@ jobs:
62104
${{ env.REGISTRY_DOCKER }}/ethoslink/postgres-backrest
63105
${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }}
64106
tags: |
65-
type=schedule
66-
type=ref,event=branch
67-
type=ref,event=tag
68107
type=ref,event=pr
69-
type=semver,pattern={{version}}
70-
type=semver,pattern={{major}}.{{minor}}
71108
type=raw,value=latest,enable={{is_default_branch}}
72-
type=raw,value=${{ steps.version.outputs.VERSION }}
109+
type=raw,value=${{ steps.version.outputs.IMAGE_VERSION }}
73110
type=sha
111+
labels: |
112+
org.opencontainers.image.version=${{ steps.version.outputs.IMAGE_VERSION }}
113+
org.opencontainers.image.base.name=docker.io/library/postgres:${{ steps.version.outputs.POSTGRES_VERSION }}
74114
75115
- name: Extract first Docker tag
76116
id: extract_tag
@@ -92,7 +132,7 @@ jobs:
92132
push: ${{ github.event_name != 'pull_request' }}
93133
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
94134
build-args: |
95-
POSTGRES_VERSION=${{ steps.version.outputs.VERSION }}
135+
POSTGRES_VERSION=${{ steps.version.outputs.POSTGRES_VERSION }}
96136
tags: ${{ steps.meta.outputs.tags }}
97137
labels: ${{ steps.meta.outputs.labels }}
98138
cache-from: type=gha
@@ -112,7 +152,7 @@ jobs:
112152
- name: Create Git Tag
113153
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
114154
run: |
115-
VERSION="${{ steps.version.outputs.VERSION }}"
155+
VERSION="${{ steps.version.outputs.IMAGE_VERSION }}"
116156
git tag "$VERSION"
117157
git push origin "$VERSION"
118158
@@ -122,10 +162,10 @@ jobs:
122162
env:
123163
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124164
with:
125-
tag_name: ${{ steps.version.outputs.VERSION }}
126-
release_name: PostgreSQL ${{ steps.version.outputs.VERSION }}
165+
tag_name: ${{ steps.version.outputs.IMAGE_VERSION }}
166+
release_name: Image ${{ steps.version.outputs.IMAGE_VERSION }} for PostgreSQL ${{ steps.version.outputs.POSTGRES_VERSION }}
127167
body: |
128-
PostgreSQL ${{ steps.version.outputs.VERSION }}
168+
PostgreSQL base image: ${{ steps.version.outputs.POSTGRES_VERSION }}
169+
Image version: ${{ steps.version.outputs.IMAGE_VERSION }}
129170
draft: false
130171
prerelease: false
131-

‎POSTGRES_VERSION‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
18.3

‎README.md‎

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,47 @@ To build the image locally with a specific PostgreSQL version:
133133
```bash
134134
# Using update.sh (recommended)
135135
./update.sh <version>
136-
docker build -t ethoslink/postgres-backrest:<version> .
136+
docker build --build-arg POSTGRES_VERSION=<version> -t ethoslink/postgres-backrest:<version> .
137137

138138
# Or directly with build-arg
139139
docker build --build-arg POSTGRES_VERSION=18.2 -t ethoslink/postgres-backrest:18.2 .
140140
```
141141

142+
To publish the multi-arch image for `linux/amd64` and `linux/arm64`, use
143+
Buildx:
144+
145+
```bash
146+
docker login docker.io
147+
docker login ghcr.io
148+
./build.sh
149+
```
150+
151+
The script pushes both the version tag and `latest` to Docker Hub and GHCR.
152+
`POSTGRES_VERSION` is the upstream base image tag. `IMAGE_VERSION` is this
153+
image's published release tag. Keep them separate when rebuilding this image on
154+
the same PostgreSQL release.
155+
156+
For image-only rebuilds, prefer a revision suffix such as `18.3-r1` over
157+
`18.3.1`, because `18.3.1` can be mistaken for an upstream PostgreSQL patch
158+
version.
159+
160+
Override `PLATFORMS`, `POSTGRES_VERSION`, `IMAGE_VERSION`, `PUBLISH_LATEST`,
161+
or `IMAGES` when needed:
162+
163+
```bash
164+
PLATFORMS=linux/amd64,linux/arm64 \
165+
POSTGRES_VERSION=18.3 \
166+
IMAGE_VERSION=18.3-r1 \
167+
IMAGES="docker.io/ethoslink/postgres-backrest ghcr.io/ethos-link/postgres-backrest" \
168+
./build.sh
169+
```
170+
171+
`build.sh` refuses to publish an image version that already exists in any
172+
target registry. Use a new `IMAGE_VERSION` for normal releases.
173+
174+
The GitHub Actions publish workflow also builds and pushes
175+
`linux/amd64,linux/arm64` on `master`.
176+
142177
## Security & Maintenance
143178

144179
The image is based on the official Debian-based PostgreSQL image and installs `pgbackrest` from the official apt repositories.
@@ -159,7 +194,7 @@ The image is published to:
159194
To bump the PostgreSQL version and create a new release:
160195

161196
```bash
162-
# Bump the version (updates Dockerfile + VERSION file)
197+
# Bump the base and image versions
163198
./update.sh 18.3
164199

165200
# Commit and push
@@ -169,16 +204,26 @@ git push origin <branch>
169204

170205
Then create a PR and merge to `master`. GitHub Actions will:
171206

172-
1. Build and push the Docker image with the new version tag
173-
2. Create a git tag (e.g., `18.3`)
174-
3. Create a GitHub Release with "PostgreSQL 18.3"
207+
1. Refuse to publish if the image version tag already exists
208+
2. Build and push the Docker image with the new image version tag
209+
3. Create a git tag (e.g., `18.3`)
210+
4. Create a GitHub Release
211+
212+
For an image-only rebuild on the same PostgreSQL base version, pass a revision:
213+
214+
```bash
215+
./update.sh 18.3 -r=1
216+
```
217+
218+
This keeps `POSTGRES_VERSION=18.3` and sets `IMAGE_VERSION=18.3-r1`.
175219

176-
You can also pass a version manually to trigger a workflow dispatch:
220+
You can also pass a base version and revision manually to trigger a workflow
221+
dispatch:
177222

178223
```bash
179224
# Via GitHub UI to manually trigger a build
180225
# Or via CLI:
181-
gh workflow run publish.yml -f postgres_version=18.3
226+
gh workflow run publish.yml -f postgres_version=18.3 -f revision=1
182227
```
183228

184229
## License

‎build.sh‎

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,50 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Build and push the postgres-backrest image to multiple registries
4+
# Build and push a multi-arch postgres-backrest image to multiple registries.
55

66
# Default values
7-
POSTGRES_VERSION="${POSTGRES_VERSION:-18.1}"
8-
IMAGE_NAME="${IMAGE_NAME:-postgres-backrest}"
9-
REGISTRIES="${REGISTRIES:-docker.io ghcr.io}"
7+
POSTGRES_VERSION="${POSTGRES_VERSION:-$(cat POSTGRES_VERSION)}"
8+
IMAGE_VERSION="${IMAGE_VERSION:-$(cat VERSION)}"
9+
PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}"
10+
IMAGES="${IMAGES:-docker.io/ethoslink/postgres-backrest ghcr.io/ethos-link/postgres-backrest}"
11+
BUILDER="${BUILDER:-postgres-backrest-builder}"
12+
ALLOW_TAG_OVERWRITE="${ALLOW_TAG_OVERWRITE:-0}"
13+
PUBLISH_LATEST="${PUBLISH_LATEST:-1}"
14+
15+
for image in $IMAGES; do
16+
if [ "${ALLOW_TAG_OVERWRITE}" != "1" ] && docker buildx imagetools inspect "${image}:${IMAGE_VERSION}" >/dev/null 2>&1; then
17+
echo "Refusing to overwrite existing image tag: ${image}:${IMAGE_VERSION}" >&2
18+
echo "Use a new IMAGE_VERSION, or set ALLOW_TAG_OVERWRITE=1 for an explicit overwrite." >&2
19+
exit 1
20+
fi
21+
done
1022

11-
# Function to build and push to a registry
12-
build_and_push() {
13-
local registry="$1"
14-
local full_image="${registry}/${IMAGE_NAME}:${POSTGRES_VERSION}"
23+
if ! docker buildx inspect "${BUILDER}" >/dev/null 2>&1; then
24+
docker buildx create --name "${BUILDER}" --driver docker-container --use
25+
else
26+
docker buildx use "${BUILDER}"
27+
fi
1528

16-
echo "Building ${full_image}..."
17-
DOCKER_BUILDKIT=1 docker build --build-arg POSTGRES_VERSION="${POSTGRES_VERSION}" -t "${full_image}" .
29+
docker buildx inspect --bootstrap >/dev/null
1830

19-
echo "Pushing ${full_image}..."
20-
docker push "${full_image}"
31+
build_args=(
32+
--platform "${PLATFORMS}"
33+
--build-arg "POSTGRES_VERSION=${POSTGRES_VERSION}"
34+
--label "org.opencontainers.image.version=${IMAGE_VERSION}"
35+
--label "org.opencontainers.image.base.name=docker.io/library/postgres:${POSTGRES_VERSION}"
36+
--push
37+
)
2138

22-
# Also tag as latest if it's the default version
23-
if [ "${POSTGRES_VERSION}" = "18.1" ]; then
24-
docker tag "${full_image}" "${registry}/${IMAGE_NAME}:latest"
25-
docker push "${registry}/${IMAGE_NAME}:latest"
26-
fi
27-
}
28-
29-
# Login to registries (you need to set credentials)
30-
for registry in $REGISTRIES; do
31-
echo "Logging into ${registry}..."
32-
# You need to set DOCKER_USERNAME and DOCKER_PASSWORD or use docker login manually
33-
if [ -n "${DOCKER_USERNAME:-}" ] && [ -n "${DOCKER_PASSWORD:-}" ]; then
34-
echo "${DOCKER_PASSWORD}" | docker login "${registry}" -u "${DOCKER_USERNAME}" --password-stdin
35-
else
36-
echo "Please set DOCKER_USERNAME and DOCKER_PASSWORD or login manually to ${registry}"
37-
exit 1
39+
for image in $IMAGES; do
40+
build_args+=(--tag "${image}:${IMAGE_VERSION}")
41+
42+
if [ "${PUBLISH_LATEST}" = "1" ]; then
43+
build_args+=(--tag "${image}:latest")
3844
fi
3945
done
4046

41-
# Build and push to each registry
42-
for registry in $REGISTRIES; do
43-
build_and_push "$registry"
44-
done
47+
echo "Building ${IMAGES}:${IMAGE_VERSION} from postgres:${POSTGRES_VERSION} for ${PLATFORMS}..."
48+
docker buildx build "${build_args[@]}" .
4549

46-
echo "Done."
50+
echo "Published ${IMAGE_VERSION} for ${PLATFORMS}."

‎update.sh‎

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,65 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Update the PostgreSQL version in the Dockerfile
4+
# Update the PostgreSQL base image version and image release tag.
55

6-
if [ $# -ne 1 ]; then
7-
echo "Usage: $0 <postgres_version>"
8-
echo "Example: $0 17.0"
6+
usage() {
7+
echo "Usage: $0 <postgres_version> [-r=<revision>|--revision=<revision>]"
8+
echo "Examples:"
9+
echo " $0 18.4"
10+
echo " $0 18.3 -r=1"
911
exit 1
12+
}
13+
14+
if [ $# -lt 1 ]; then
15+
usage
16+
fi
17+
18+
NEW_POSTGRES_VERSION="$1"
19+
REVISION=""
20+
21+
shift
22+
23+
while [ $# -gt 0 ]; do
24+
case "$1" in
25+
-r=*|--revision=*)
26+
REVISION="${1#*=}"
27+
;;
28+
-r|--revision)
29+
shift
30+
if [ $# -eq 0 ]; then
31+
usage
32+
fi
33+
REVISION="$1"
34+
;;
35+
-*)
36+
usage
37+
;;
38+
*)
39+
usage
40+
;;
41+
esac
42+
shift
43+
done
44+
45+
if [ -n "${REVISION}" ]; then
46+
if ! [[ "${REVISION}" =~ ^[1-9][0-9]*$ ]]; then
47+
echo "Revision must be a positive integer." >&2
48+
exit 1
49+
fi
50+
51+
NEW_IMAGE_VERSION="${NEW_POSTGRES_VERSION}-r${REVISION}"
1052
fi
1153

12-
NEW_VERSION="$1"
54+
NEW_IMAGE_VERSION="${NEW_IMAGE_VERSION:-$NEW_POSTGRES_VERSION}"
1355

1456
# Update the default ARG in Dockerfile
15-
sed -i "s/ARG POSTGRES_VERSION=.*/ARG POSTGRES_VERSION=${NEW_VERSION}/" Dockerfile
57+
sed -i "s/ARG POSTGRES_VERSION=.*/ARG POSTGRES_VERSION=${NEW_POSTGRES_VERSION}/" Dockerfile
1658

17-
# Update VERSION file
18-
echo "${NEW_VERSION}" > VERSION
59+
# Update version files
60+
echo "${NEW_POSTGRES_VERSION}" > POSTGRES_VERSION
61+
echo "${NEW_IMAGE_VERSION}" > VERSION
1962

20-
echo "Updated Dockerfile and VERSION file to use PostgreSQL version ${NEW_VERSION}"
21-
echo "You can now build and test the image."
63+
echo "Updated PostgreSQL base version to ${NEW_POSTGRES_VERSION}"
64+
echo "Updated image release version to ${NEW_IMAGE_VERSION}"
65+
echo "You can now build and test the image."

0 commit comments

Comments
 (0)