Commit e6c2d68

Antonio Mika  ·  2026-10-11 18:56:08 -0400 EDT
parent 30ea27e
ci: build images with docker bake and cache go builds across runs
9 files changed,  +292, -289
+0, -70
......@@ -1,70 +0,0 @@
1-name: Run a docker build
2-
3-description: Runs a docker build in a composite action
4-
5-inputs:
6- app:
7- description: The pico app to build
8- required: true
9- platforms:
10- description: The docker platforms to build for
11- required: true
12- default: |
13- linux/amd64
14- linux/arm64
15- registry:
16- description: The docker registry to use
17- required: true
18- default: ghcr.io
19- web:
20- description: Whether or not to build the web image
21- required: true
22- default: true
23- type: boolean
24- ssh:
25- description: Whether or not to build the ssh image
26- required: true
27- default: true
28- type: boolean
29-
30-runs:
31- using: composite
32- steps:
33- - name: Collect web image metadata
34- id: webmeta
35- uses: docker/metadata-action@v4
36- with:
37- images: ${{ inputs.REGISTRY }}/${{ github.repository }}/${{ inputs.app }}-web
38- - name: Collect ssh image metadata
39- id: sshmeta
40- uses: docker/metadata-action@v4
41- with:
42- images: ${{ inputs.REGISTRY }}/${{ github.repository }}/${{ inputs.app }}-ssh
43- - name: Build and push web
44- if: inputs.web == 'true'
45- uses: docker/build-push-action@v3
46- with:
47- context: .
48- push: true
49- tags: ${{ steps.webmeta.outputs.tags }}
50- labels: ${{ steps.webmeta.outputs.labels }}
51- target: release-web
52- platforms: ${{ inputs.platforms }}
53- cache-from: type=gha
54- cache-to: type=gha,mode=max
55- build-args: |
56- APP=${{ inputs.app }}
57- - name: Build and push ssh
58- if: inputs.ssh == 'true'
59- uses: docker/build-push-action@v3
60- with:
61- context: .
62- push: true
63- tags: ${{ steps.sshmeta.outputs.tags }}
64- labels: ${{ steps.sshmeta.outputs.labels }}
65- target: release-ssh
66- platforms: ${{ inputs.platforms }}
67- cache-from: type=gha
68- cache-to: type=gha,mode=max
69- build-args: |
70- APP=${{ inputs.app }}
+0, -34
......@@ -1,34 +0,0 @@
1-name: Setup main action
2-
3-description: Runs the setup required for docker building
4-
5-inputs:
6- registry:
7- description: The docker registry to use
8- required: true
9- default: ghcr.io
10- username:
11- description: The docker registry to use
12- required: true
13- password:
14- description: The docker registry to use
15- required: true
16-
17-runs:
18- using: composite
19- steps:
20- - name: Set up QEMU
21- uses: docker/setup-qemu-action@v2
22- with:
23- platforms: all
24- - name: Set up Docker Buildx
25- id: buildx
26- uses: docker/setup-buildx-action@v2
27- with:
28- version: latest
29- - name: Login to Docker Hub
30- uses: docker/login-action@v2
31- with:
32- registry: ${{ inputs.registry }}
33- username: ${{ inputs.username }}
34- password: ${{ inputs.password }}
+3, -2
......@@ -6,11 +6,12 @@ runs:
66 using: composite
77 steps:
88 - name: Set up Go
9- uses: actions/setup-go@v3
9+ uses: actions/setup-go@v7
1010 with:
1111 go-version-file: go.mod
12+ cache-dependency-path: go.sum
1213 - name: Lint the codebase
13- uses: golangci/golangci-lint-action@v8
14+ uses: golangci/golangci-lint-action@v9
1415 with:
1516 version: latest
1617 - name: Run tests
+47, -0
......@@ -0,0 +1,47 @@
1+name: Build bouncer
2+
3+on:
4+ workflow_dispatch:
5+ push:
6+ branches:
7+ - main
8+ tags:
9+ - v*
10+ paths:
11+ - bouncer/**
12+ - .github/workflows/bouncer.yml
13+
14+env:
15+ REGISTRY: ghcr.io
16+
17+jobs:
18+ build:
19+ runs-on: ubuntu-26.04
20+ steps:
21+ - name: Checkout repo
22+ uses: actions/checkout@v7
23+ - name: Set up Docker Buildx
24+ uses: docker/setup-buildx-action@v4
25+ - name: Login to the registry
26+ uses: docker/login-action@v4
27+ with:
28+ registry: ${{ env.REGISTRY }}
29+ username: ${{ github.actor }}
30+ password: ${{ secrets.GITHUB_TOKEN }}
31+ - name: Collect image metadata
32+ id: meta
33+ uses: docker/metadata-action@v6
34+ with:
35+ images: ${{ env.REGISTRY }}/${{ github.repository }}/bouncer
36+ - name: Build and push
37+ uses: docker/build-push-action@v7
38+ with:
39+ context: ./bouncer
40+ push: true
41+ tags: ${{ steps.meta.outputs.tags }}
42+ labels: ${{ steps.meta.outputs.labels }}
43+ platforms: |
44+ linux/amd64
45+ linux/arm64
46+ cache-from: type=gha,scope=bouncer
47+ cache-to: type=gha,scope=bouncer,mode=max
+44, -181
......@@ -10,202 +10,65 @@ on:
1010
1111 env:
1212 REGISTRY: ghcr.io
13- PLATFORMS: |
14- linux/amd64
15- linux/arm64
1613
1714 jobs:
1815 test:
1916 runs-on: ubuntu-26.04
2017 steps:
2118 - name: Checkout repo
22- uses: actions/checkout@v3
19+ uses: actions/checkout@v7
2320 - name: Run tests and lint
2421 uses: ./.github/actions/test
25- build-main:
22+ build:
2623 runs-on: ubuntu-26.04
2724 needs: test
28- strategy:
29- matrix:
30- APP: [prose, pastes, pgs, feeds, pipe]
3125 steps:
3226 - name: Checkout repo
33- uses: actions/checkout@v3
34- - name: Setup docker
35- uses: ./.github/actions/setup
27+ uses: actions/checkout@v7
28+ # The Dockerfiles cross-compile on the build platform, so no QEMU.
29+ - name: Set up Docker Buildx
30+ id: buildx
31+ uses: docker/setup-buildx-action@v4
32+ - name: Login to the registry
33+ uses: docker/login-action@v4
3634 with:
3735 registry: ${{ env.REGISTRY }}
3836 username: ${{ github.actor }}
3937 password: ${{ secrets.GITHUB_TOKEN }}
40- - name: Run docker build for ${{ matrix.APP }}
41- uses: ./.github/actions/build
42- with:
43- app: ${{ matrix.APP }}
44- platforms: ${{ env.PLATFORMS }}
45- registry: ${{ env.REGISTRY }}
46- build-auth:
47- runs-on: ubuntu-26.04
48- needs: test
49- steps:
50- - name: Checkout repo
51- uses: actions/checkout@v3
52- - name: Setup docker
53- uses: ./.github/actions/setup
54- with:
55- registry: ${{ env.REGISTRY }}
56- username: ${{ github.actor }}
57- password: ${{ secrets.GITHUB_TOKEN }}
58- - name: Run docker build for auth
59- uses: ./.github/actions/build
60- with:
61- app: auth
62- platforms: ${{ env.PLATFORMS }}
63- registry: ${{ env.REGISTRY }}
64- ssh: false
65- build-pico:
66- runs-on: ubuntu-26.04
67- needs: test
68- steps:
69- - name: Checkout repo
70- uses: actions/checkout@v3
71- - name: Setup docker
72- uses: ./.github/actions/setup
73- with:
74- registry: ${{ env.REGISTRY }}
75- username: ${{ github.actor }}
76- password: ${{ secrets.GITHUB_TOKEN }}
77- - name: Run docker build for pico
78- uses: ./.github/actions/build
79- with:
80- app: pico
81- platforms: ${{ env.PLATFORMS }}
82- registry: ${{ env.REGISTRY }}
83- web: false
84- build-pgs-cdn:
85- runs-on: ubuntu-26.04
86- needs: test
87- steps:
88- - name: Checkout repo
89- uses: actions/checkout@v3
90- - name: Setup docker
91- uses: ./.github/actions/setup
92- with:
93- registry: ${{ env.REGISTRY }}
94- username: ${{ github.actor }}
95- password: ${{ secrets.GITHUB_TOKEN }}
96- - name: Collect web image metadata
97- id: pgscdnmeta
98- uses: docker/metadata-action@v4
99- with:
100- images: ${{ env.REGISTRY }}/${{ github.repository }}/pgs-cdn
101- - name: Build and push
102- uses: docker/build-push-action@v3
103- with:
104- context: .
105- file: Dockerfile.cdn
106- push: true
107- target: release-web
108- tags: ${{ steps.pgscdnmeta.outputs.tags }}
109- labels: ${{ steps.pgscdnmeta.outputs.labels }}
110- platforms: ${{ env.PLATFORMS }}
111- cache-from: type=gha
112- cache-to: type=gha,mode=max
113- build-pgs-standalone:
114- runs-on: ubuntu-26.04
115- needs: test
116- steps:
117- - name: Checkout repo
118- uses: actions/checkout@v3
119- - name: Setup docker
120- uses: ./.github/actions/setup
121- with:
122- registry: ${{ env.REGISTRY }}
123- username: ${{ github.actor }}
124- password: ${{ secrets.GITHUB_TOKEN }}
125- - name: Collect web image metadata
126- id: pgsstandalonemeta
127- uses: docker/metadata-action@v4
128- with:
129- images: ${{ env.REGISTRY }}/picosh/pgs
38+ - name: Collect image metadata
39+ id: meta
40+ uses: docker/metadata-action@v6
41+ # The layer cache doesn't keep RUN --mount=type=cache, so the Go module
42+ # and build caches are saved separately and loaded into the builder.
43+ - name: Cache Go modules and builds
44+ id: cache
45+ uses: actions/cache@v5
46+ with:
47+ path: ${{ runner.temp }}/go-cache
48+ key: go-docker-${{ hashFiles('go.sum') }}-${{ github.sha }}
49+ restore-keys: |
50+ go-docker-${{ hashFiles('go.sum') }}-
51+ go-docker-
52+ - name: Load Go caches into the builder
53+ uses: reproducible-containers/buildkit-cache-dance@v3
54+ with:
55+ builder: ${{ steps.buildx.outputs.name }}
56+ cache-map: |
57+ {
58+ "${{ runner.temp }}/go-cache/pkg": "/go/pkg/",
59+ "${{ runner.temp }}/go-cache/build": "/root/.cache/"
60+ }
61+ scratch-dir: ${{ runner.temp }}/cache-dance
62+ skip-extraction: ${{ steps.cache.outputs.cache-hit }}
13063 - name: Build and push
131- uses: docker/build-push-action@v3
132- with:
133- context: .
134- file: Dockerfile.standalone
135- push: true
136- target: release
137- tags: ${{ steps.pgsstandalonemeta.outputs.tags }}
138- labels: ${{ steps.pgsstandalonemeta.outputs.labels }}
139- platforms: ${{ env.PLATFORMS }}
140- cache-from: type=gha
141- cache-to: type=gha,mode=max
142- build-bouncer:
143- runs-on: ubuntu-26.04
144- needs: test
145- steps:
146- - name: Checkout repo
147- uses: actions/checkout@v3
148- - name: Get changed files
149- id: changed-files
150- uses: tj-actions/changed-files@v40
151- with:
152- files: bouncer/**
153- - name: Setup docker
154- if: steps.changed-files.outputs.any_changed == 'true'
155- uses: ./.github/actions/setup
156- with:
157- registry: ${{ env.REGISTRY }}
158- username: ${{ github.actor }}
159- password: ${{ secrets.GITHUB_TOKEN }}
160- - name: Collect bouncer image metadata
161- if: steps.changed-files.outputs.any_changed == 'true'
162- id: bouncermeta
163- uses: docker/metadata-action@v4
164- with:
165- images: ${{ env.REGISTRY }}/${{ github.repository }}/bouncer
166- - name: Build and push
167- if: steps.changed-files.outputs.any_changed == 'true'
168- uses: docker/build-push-action@v3
169- with:
170- context: ./bouncer
171- push: true
172- tags: ${{ steps.bouncermeta.outputs.tags }}
173- labels: ${{ steps.bouncermeta.outputs.labels }}
174- platforms: ${{ env.PLATFORMS }}
175- cache-from: type=gha
176- cache-to: type=gha,mode=max
177- build-caddy:
178- runs-on: ubuntu-26.04
179- needs: test
180- steps:
181- - name: Checkout repo
182- uses: actions/checkout@v3
183- - name: Get changed files
184- id: changed-files
185- uses: tj-actions/changed-files@v40
186- with:
187- files: caddy/**
188- - name: Setup docker
189- if: steps.changed-files.outputs.any_changed == 'true'
190- uses: ./.github/actions/setup
191- with:
192- registry: ${{ env.REGISTRY }}
193- username: ${{ github.actor }}
194- password: ${{ secrets.GITHUB_TOKEN }}
195- - name: Collect caddy image metadata
196- if: steps.changed-files.outputs.any_changed == 'true'
197- id: caddymeta
198- uses: docker/metadata-action@v4
199- with:
200- images: ${{ env.REGISTRY }}/${{ github.repository }}/caddy
201- - name: Build and push
202- if: steps.changed-files.outputs.any_changed == 'true'
203- uses: docker/build-push-action@v3
204- with:
205- context: ./caddy
64+ uses: docker/bake-action@v7
65+ env:
66+ REGISTRY: ${{ env.REGISTRY }}
67+ TAGS: ${{ steps.meta.outputs.tag-names }}
68+ GHA_CACHE: "true"
69+ with:
70+ source: .
71+ files: |
72+ ./docker-bake.hcl
73+ cwd://${{ steps.meta.outputs.bake-file-labels }}
20674 push: true
207- tags: ${{ steps.caddymeta.outputs.tags }}
208- labels: ${{ steps.caddymeta.outputs.labels }}
209- platforms: ${{ env.PLATFORMS }}
210- cache-from: type=gha
211- cache-to: type=gha,mode=max
+47, -0
......@@ -0,0 +1,47 @@
1+name: Build caddy
2+
3+on:
4+ workflow_dispatch:
5+ push:
6+ branches:
7+ - main
8+ tags:
9+ - v*
10+ paths:
11+ - caddy/**
12+ - .github/workflows/caddy.yml
13+
14+env:
15+ REGISTRY: ghcr.io
16+
17+jobs:
18+ build:
19+ runs-on: ubuntu-26.04
20+ steps:
21+ - name: Checkout repo
22+ uses: actions/checkout@v7
23+ - name: Set up Docker Buildx
24+ uses: docker/setup-buildx-action@v4
25+ - name: Login to the registry
26+ uses: docker/login-action@v4
27+ with:
28+ registry: ${{ env.REGISTRY }}
29+ username: ${{ github.actor }}
30+ password: ${{ secrets.GITHUB_TOKEN }}
31+ - name: Collect image metadata
32+ id: meta
33+ uses: docker/metadata-action@v6
34+ with:
35+ images: ${{ env.REGISTRY }}/${{ github.repository }}/caddy
36+ - name: Build and push
37+ uses: docker/build-push-action@v7
38+ with:
39+ context: ./caddy
40+ push: true
41+ tags: ${{ steps.meta.outputs.tags }}
42+ labels: ${{ steps.meta.outputs.labels }}
43+ platforms: |
44+ linux/amd64
45+ linux/arm64
46+ cache-from: type=gha,scope=caddy
47+ cache-to: type=gha,scope=caddy,mode=max
+1, -1
......@@ -10,6 +10,6 @@ jobs:
1010 runs-on: ubuntu-26.04
1111 steps:
1212 - name: Checkout repo
13- uses: actions/checkout@v3
13+ uses: actions/checkout@v7
1414 - name: Run tests and lint
1515 uses: ./.github/actions/test
+41, -1
......@@ -7,6 +7,7 @@ DOCKER_TAG?=$(shell git log --format="%H" -n 1)
77 DOCKER_PLATFORM?=linux/amd64,linux/arm64
88 DOCKER_CMD?=docker
99 DOCKER_BUILDX_BUILD?=$(DOCKER_CMD) buildx build --push --platform $(DOCKER_PLATFORM) -t
10+DOCKER_BAKE?=TAGS=$(DOCKER_TAG) $(DOCKER_CMD) buildx bake -f docker-bake.hcl --push --set "*.platform=$(DOCKER_PLATFORM)"
1011 WRITE?=0
1112
1213 smol:
......@@ -49,6 +50,44 @@ else
4950 endif
5051 .PHONY: bp-setup
5152
53+# Podman has no bake, so it builds each image on its own.
54+ifeq ($(DOCKER_CMD),docker)
55+bp-caddy: bp-setup
56+ $(DOCKER_BAKE) caddy
57+.PHONY: bp-caddy
58+
59+bp-auth: bp-setup
60+ $(DOCKER_BAKE) auth-web
61+.PHONY: bp-auth
62+
63+bp-pgs-cdn: bp-setup
64+ $(DOCKER_BAKE) pgs-cdn
65+.PHONY: bp-pgs-cdn
66+
67+bp-pgs-standalone: bp-setup
68+ $(DOCKER_BAKE) pgs-standalone
69+.PHONY: bp-pgs-standalone
70+
71+bp-pico: bp-setup
72+ $(DOCKER_BAKE) pico-ssh
73+.PHONY: bp-pico
74+
75+bp-bouncer: bp-setup
76+ $(DOCKER_BAKE) bouncer
77+.PHONY: bp-bouncer
78+
79+bp-ssh-%: bp-setup
80+ $(DOCKER_BAKE) "$*-ssh"
81+.PHONY: bp-ssh-%
82+
83+bp-%: bp-setup
84+ $(DOCKER_BAKE) "$*-web" "$*-ssh"
85+.PHONY: bp-%
86+
87+bp-all: bp-setup
88+ $(DOCKER_BAKE) default bouncer
89+.PHONY: bp-all
90+else
5291 bp-caddy: bp-setup
5392 $(DOCKER_BUILDX_BUILD) ghcr.io/picosh/pico/caddy:$(DOCKER_TAG) ./caddy
5493 .PHONY: bp-caddy
......@@ -82,8 +121,9 @@ bp-%: bp-setup
82121 $(DOCKER_BUILDX_BUILD) "ghcr.io/picosh/pico/$*-web:$(DOCKER_TAG)" --build-arg "APP=$*" --target release-web .
83122 .PHONY: bp-%
84123
85-bp-all: bp-prose bp-pastes bp-feeds bp-pgs bp-auth bp-bouncer bp-pipe bp-pgs-cdn
124+bp-all: bp-prose bp-pastes bp-feeds bp-pgs bp-auth bp-bouncer bp-pipe bp-pgs-cdn bp-pico bp-pgs-standalone
86125 .PHONY: bp-all
126+endif
87127
88128 LDFLAGS?=-s -w
89129
+109, -0
......@@ -0,0 +1,109 @@
1+variable "REGISTRY" {
2+ default = "ghcr.io"
3+}
4+
5+# Tag names without the image, separated by newlines or commas.
6+variable "TAGS" {
7+ default = "latest"
8+}
9+
10+# Set to "true" in GitHub Actions to read and write the layer cache.
11+variable "GHA_CACHE" {
12+ default = ""
13+}
14+
15+function "tags" {
16+ params = [image]
17+ result = formatlist("${REGISTRY}/${image}:%s", compact(split(",", replace(TAGS, "\n", ","))))
18+}
19+
20+# Each image gets its own scope so builds don't overwrite each other's cache.
21+function "cache_from" {
22+ params = [scope]
23+ result = GHA_CACHE == "true" ? ["type=gha,scope=${scope}"] : []
24+}
25+
26+function "cache_to" {
27+ params = [scope]
28+ result = GHA_CACHE == "true" ? ["type=gha,scope=${scope},mode=max"] : []
29+}
30+
31+# Filled in with labels by docker/metadata-action.
32+target "docker-metadata-action" {}
33+
34+target "_common" {
35+ inherits = ["docker-metadata-action"]
36+ context = "."
37+ platforms = ["linux/amd64", "linux/arm64"]
38+}
39+
40+group "default" {
41+ targets = ["apps", "auth-web", "pico-ssh", "pgs-cdn", "pgs-standalone"]
42+}
43+
44+target "apps" {
45+ name = "${app}-${kind}"
46+ matrix = {
47+ app = ["prose", "pastes", "pgs", "feeds", "pipe"]
48+ kind = ["web", "ssh"]
49+ }
50+ inherits = ["_common"]
51+ target = "release-${kind}"
52+ args = { APP = app }
53+ tags = tags("picosh/pico/${app}-${kind}")
54+ cache-from = cache_from("${app}-${kind}")
55+ cache-to = cache_to("${app}-${kind}")
56+}
57+
58+target "auth-web" {
59+ inherits = ["_common"]
60+ target = "release-web"
61+ args = { APP = "auth" }
62+ tags = tags("picosh/pico/auth-web")
63+ cache-from = cache_from("auth-web")
64+ cache-to = cache_to("auth-web")
65+}
66+
67+target "pico-ssh" {
68+ inherits = ["_common"]
69+ target = "release-ssh"
70+ args = { APP = "pico" }
71+ tags = tags("picosh/pico/pico-ssh")
72+ cache-from = cache_from("pico-ssh")
73+ cache-to = cache_to("pico-ssh")
74+}
75+
76+target "pgs-cdn" {
77+ inherits = ["_common"]
78+ dockerfile = "Dockerfile.cdn"
79+ target = "release-web"
80+ tags = tags("picosh/pico/pgs-cdn")
81+ cache-from = cache_from("pgs-cdn")
82+ cache-to = cache_to("pgs-cdn")
83+}
84+
85+target "pgs-standalone" {
86+ inherits = ["_common"]
87+ dockerfile = "Dockerfile.standalone"
88+ target = "release"
89+ tags = tags("picosh/pgs")
90+ cache-from = cache_from("pgs-standalone")
91+ cache-to = cache_to("pgs-standalone")
92+}
93+
94+# caddy and bouncer have their own workflows, so they stay out of the default group.
95+target "caddy" {
96+ inherits = ["_common"]
97+ context = "./caddy"
98+ tags = tags("picosh/pico/caddy")
99+ cache-from = cache_from("caddy")
100+ cache-to = cache_to("caddy")
101+}
102+
103+target "bouncer" {
104+ inherits = ["_common"]
105+ context = "./bouncer"
106+ tags = tags("picosh/pico/bouncer")
107+ cache-from = cache_from("bouncer")
108+ cache-to = cache_to("bouncer")
109+}