Skip to content

Commit 71e8445

Browse files
authored
feat: Add container publishing (#6)
1 parent 95fcf4e commit 71e8445

4 files changed

Lines changed: 285 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Publishes an 'edge' container image to a registry, tagging with the current date and commit hash.
2+
name: Publish Container Image
3+
4+
on:
5+
workflow_call:
6+
inputs:
7+
image-name:
8+
description: 'Container image name (e.g. my-app)'
9+
required: true
10+
type: string
11+
image-description:
12+
description: 'Description used in OCI annotations'
13+
required: false
14+
default: ''
15+
type: string
16+
registry:
17+
description: 'Container registry host (e.g. ghcr.io)'
18+
required: false
19+
default: 'ghcr.io'
20+
type: string
21+
repo-owner:
22+
description: 'Owner/namespace for the image (defaults to calling repo owner)'
23+
required: false
24+
default: ''
25+
type: string
26+
context:
27+
description: 'Build context passed to Docker (e.g. . or ./app)'
28+
required: false
29+
default: '.'
30+
type: string
31+
dockerfile:
32+
description: 'Relative path to Dockerfile'
33+
required: false
34+
default: 'Dockerfile'
35+
type: string
36+
platforms:
37+
description: 'Target platforms for the image (comma-separated)'
38+
required: false
39+
default: 'linux/amd64,linux/arm64'
40+
type: string
41+
42+
env:
43+
image-name: ${{ inputs.image-name }}
44+
image-description: ${{ inputs.image-description }}
45+
repo-owner: ${{ inputs.repo-owner || github.repository_owner }}
46+
registry: ${{ inputs.registry }}
47+
context: ${{ inputs.context }}
48+
dockerfile: ${{ inputs.dockerfile }}
49+
platforms: ${{ inputs.platforms }}
50+
51+
jobs:
52+
publish-container:
53+
runs-on: ubuntu-latest
54+
permissions:
55+
packages: write
56+
steps:
57+
- name: Check out the repo
58+
uses: actions/checkout@v4
59+
60+
- name: Set up QEMU
61+
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
62+
63+
- name: Set up Docker Buildx
64+
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
65+
66+
- name: Docker Login
67+
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
68+
with:
69+
registry: ${{ env.registry }}
70+
username: ${{ github.actor }}
71+
password: ${{ secrets.GITHUB_TOKEN }}
72+
73+
- name: Docker Metadata action
74+
id: meta
75+
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
76+
env:
77+
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
78+
with:
79+
images: ${{ env.registry }}/${{ env.repo-owner }}/${{ env.image-name }}
80+
# Tag notes:
81+
# - RFC3339 is not suitable for docker tags, so we squash the date
82+
# - We tag both the short (7-char prefixed) and full sha commit hashes; both are useful
83+
# - `edge` represents latest main branch commit (potentially unstable)
84+
tags: |
85+
type=sha
86+
${{ github.sha }}
87+
type=raw,value={{date 'YYYYMMDDHHmmss[Z]'}}
88+
edge
89+
# Label notes:
90+
# - Static labels are applied in the Dockerfile
91+
# - Date format in `org.opencontainers.image.created` must be RFC3339
92+
# - version should be considered a semver candidate only, unless revision aligns with a git tag
93+
labels: |
94+
org.opencontainers.image.revision={{sha}}
95+
org.opencontainers.image.created={{date 'YYYY-MM-DD HH:mm:ss[Z]'}}
96+
annotations: |
97+
org.opencontainers.image.description=${{ env.image-description }}
98+
99+
- name: Build and push Docker images
100+
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
101+
with:
102+
context: ${{ env.context }}
103+
file: ${{ env.dockerfile }}
104+
push: true
105+
platforms: ${{ env.platforms }}
106+
tags: ${{ steps.meta.outputs.tags }}
107+
labels: ${{ steps.meta.outputs.labels }}
108+
annotations: ${{ steps.meta.outputs.annotations }}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Publish Semver Container
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
image-name:
7+
description: 'Container image name (e.g. my-app)'
8+
required: true
9+
type: string
10+
registry:
11+
description: 'Container registry host (e.g. ghcr.io)'
12+
required: false
13+
default: 'ghcr.io'
14+
type: string
15+
release-name-prefix:
16+
description: 'Prefix for the GitHub Release title'
17+
required: false
18+
default: ''
19+
type: string
20+
21+
env:
22+
image-name: ${{ inputs.image-name }}
23+
registry: ${{ inputs.registry }}
24+
release-name-prefix: ${{ inputs.release-name-prefix }}
25+
26+
jobs:
27+
version-tag:
28+
runs-on: ubuntu-latest
29+
permissions:
30+
packages: write # container images
31+
contents: write # releases
32+
steps:
33+
- name: Check out the repo
34+
uses: actions/checkout@v4
35+
36+
# some docker actions need all lowercase
37+
- name: downcase repo-owner
38+
run: |
39+
echo "REPO_OWNER_LOWER=${GITHUB_REPOSITORY_OWNER,,}" >>${GITHUB_ENV}
40+
41+
- name: Parse version from tag
42+
id: version
43+
uses: release-kit/semver@97491c46500b6e758ced599794164a234b8aa08c # v2.0.7
44+
45+
# check image exists for commit
46+
- uses: tyriis/docker-image-tag-exists@71a750a41aa78e4efb0842f538140c5df5b8166f # v2.1.0
47+
with:
48+
registry: ${{ env.registry }}
49+
repository: ${{ env.REPO_OWNER_LOWER }}/${{ env.image-name }}
50+
tag: ${{ github.sha }}
51+
52+
# standard login to the container registry
53+
- name: Docker Login
54+
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
55+
with:
56+
registry: ${{ env.registry }}
57+
username: ${{github.actor}}
58+
password: ${{secrets.GITHUB_TOKEN}}
59+
60+
# We still use the metadata action to help build out our tags from the Workflow Run
61+
- name: Docker Metadata action
62+
id: meta
63+
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
64+
with:
65+
images: ${{ env.registry }}/${{ env.REPO_OWNER_LOWER }}/${{ env.image-name }}
66+
tags: | # new tags only
67+
type=semver,pattern={{version}}
68+
type=semver,pattern={{major}}
69+
type=semver,pattern={{major}}.{{minor}}
70+
71+
# apply the new tags to the existing images
72+
- name: Push updated image tags
73+
uses: akhilerm/tag-push-action@f35ff2cb99d407368b5c727adbcc14a2ed81d509 # v2.2.0
74+
with:
75+
src: ${{ env.registry }}/${{ env.REPO_OWNER_LOWER }}/${{ env.image-name }}:${{ github.sha }}
76+
dst: |
77+
${{ steps.meta.outputs.tags }}

docs/publish-container.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Publish Container Image
2+
3+
Reusable workflow that builds and pushes a multi-architecture container image to a registry (e.g. `ghcr.io`). It tags the image with:
4+
5+
- the current commit SHA
6+
- a timestamp (`YYYYMMDDHHmmssZ`)
7+
- `edge` (representing the latest main/dev build)
8+
9+
This is intended for releasing dev/edge images, not stable semver releases.
10+
11+
## Inputs
12+
13+
| Input | Required | Default | Description |
14+
|--------------------|----------|---------------------------------|-------------------------------------------------------|
15+
| `image-name` | Yes || Image name, e.g. `my-app` or `services/api`. |
16+
| `image-description`| No | `''` | Description used in OCI annotations. |
17+
| `registry` | No | `'ghcr.io'` | Registry host. |
18+
| `repo-owner` | No | calling repo owner | Owner/namespace (e.g. `Health-Informatics-UoN`). |
19+
| `context` | No | `'.'` | Docker build context. |
20+
| `dockerfile` | No | `'Dockerfile'` | Path to `Dockerfile` relative to repo root/context. |
21+
| `platforms` | No | `'linux/amd64,linux/arm64'` | Target platforms for the image. |
22+
23+
## Secrets
24+
25+
- `GITHUB_TOKEN` — Pass with `secrets: inherit` so the workflow can log in to the registry and push images.
26+
27+
## Usage
28+
29+
In your repo, add a workflow that runs on push to your main/dev branch:
30+
31+
```yaml
32+
name: Publish Dev Container
33+
34+
on:
35+
push:
36+
branches:
37+
- main
38+
39+
jobs:
40+
publish-container:
41+
uses: health-informatics-uon/workflows/.github/workflows/publish-container.yml@main
42+
with:
43+
image-name: my-service
44+
image-description: 'My service edge build'
45+
registry: ghcr.io
46+
# repo-owner: Health-Informatics-UoN # optional override
47+
context: .
48+
dockerfile: Dockerfile
49+
platforms: linux/amd64,linux/arm64
50+
secrets: inherit
51+
```
52+
53+
This will publish images like:
54+
55+
- `ghcr.io/<owner>/my-service:<sha>`
56+
- `ghcr.io/<owner>/my-service:<timestamp>`
57+
- `ghcr.io/<owner>/my-service:edge`
58+

docs/semver-container.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Semver Container Release
2+
3+
Reusable workflow that:
4+
5+
- tags an existing container image with semver tags (`x.y.z`, `x`, `x.y`) based on the git tag
6+
- verifies that a corresponding container image (tagged with the commit SHA) already exists
7+
8+
Use this when you already have an \"edge\" or commit-SHA container image (for example from the `publish-container` workflow) and you want to promote it to a semver release.
9+
10+
## Inputs
11+
12+
| Input | Required | Default | Description |
13+
|---------------------|----------|------------------------------------|------------------------------------------------------------------|
14+
| `image-name` | Yes || Image name, e.g. `my-app` or `services/api`. |
15+
| `registry` | No | `'ghcr.io'` | Registry host. |
16+
| `release-name-prefix` | No | `''` | String prefixed to the Release title (e.g. project name + space).|
17+
18+
19+
## Secrets
20+
21+
- `GITHUB_TOKEN` — Pass with `secrets: inherit` so the workflow can read tags, create releases, and push tags in the container registry.
22+
23+
## Usage
24+
25+
This workflow is designed to as part of a release process.
26+
27+
```yaml
28+
jobs:
29+
semver-container:
30+
uses: health-informatics-uon/workflows/.github/workflows/semver-container.yml@main
31+
with:
32+
image-name: my-service
33+
registry: ghcr.io
34+
release-name-prefix: 'My Service '
35+
secrets: inherit
36+
```
37+
38+
This will:
39+
40+
- check that an image `ghcr.io/<owner>/my-service:<sha>` exists
41+
- push additional tags like `1.2.3`, `1`, and `1.2` to that image
42+

0 commit comments

Comments
 (0)