1
+ name : Build Container Image
2
+
3
+ on :
4
+ workflow_call :
5
+ inputs :
6
+ registry :
7
+ description : ' Container registry'
8
+ required : true
9
+ type : string
10
+ name :
11
+ description : ' Image name, without registry or tags (repo/name)'
12
+ required : true
13
+ type : string
14
+ tag :
15
+ description : ' Tag for manifest creation'
16
+ required : true
17
+ type : string
18
+ build-runners :
19
+ description : ' JSON array of runners to build on (e.g. ["ubuntu-24.04", "ubuntu-24.04-arm"])'
20
+ required : false
21
+ type : string
22
+ default : ' ["ubuntu-24.04", "ubuntu-24.04-arm"]'
23
+ merge-runner :
24
+ description : ' Runner for merge job'
25
+ required : false
26
+ type : string
27
+ default : ' ubuntu-24.04'
28
+ dockerfile :
29
+ description : ' Path to Dockerfile'
30
+ required : false
31
+ type : string
32
+ default : ' Dockerfile'
33
+ context :
34
+ description : ' Build context path'
35
+ required : false
36
+ type : string
37
+ default : ' .'
38
+ build-args :
39
+ description : ' Build arguments as multi-line string (e.g. "ARG1=value1\nARG2=value2")'
40
+ required : false
41
+ type : string
42
+ default : ' '
43
+ build-args-for-arch :
44
+ description : ' Architecture-specific build arguments as JSON object (e.g. {"x86_64": "ARG1=value1\nARG2=value2", "aarch64": "ARG3=value3"})'
45
+ required : false
46
+ type : string
47
+ default : ' {}'
48
+ target :
49
+ description : ' Target stage in multi-stage Dockerfile'
50
+ required : false
51
+ type : string
52
+ default : ' '
53
+ buildkit-config :
54
+ description : ' BuildKit daemon configuration'
55
+ required : false
56
+ type : string
57
+ default : ' '
58
+ cache-prefix :
59
+ description : ' Prefix for cache image names'
60
+ required : false
61
+ type : string
62
+ default : ' '
63
+ update-cache :
64
+ description : ' Enable cache-to for pushing updated cache layers'
65
+ required : false
66
+ type : boolean
67
+ default : false
68
+ artifact-name :
69
+ description : ' Name of the artifact to be downloaded before build'
70
+ required : false
71
+ type : string
72
+ default : ' '
73
+ artifact-path :
74
+ description : ' Directory where the artifact should be unpacked'
75
+ required : false
76
+ type : string
77
+ default : ' .'
78
+ checkout-submodules :
79
+ description : ' Whether to checkout git submodules'
80
+ required : false
81
+ type : boolean
82
+ default : false
83
+ checkout-ref :
84
+ description : ' Git ref to checkout'
85
+ required : false
86
+ type : string
87
+ default : ' '
88
+ checkout-path :
89
+ description : ' Path to checkout'
90
+ required : false
91
+ type : string
92
+ default : ' .'
93
+
94
+ outputs :
95
+ digest :
96
+ description : ' Digest of the multi-arch image'
97
+ value : ${{ jobs.merge.outputs.digest }}
98
+ ref-with-digest :
99
+ description : ' Full image reference with digest'
100
+ value : ${{ jobs.merge.outputs.ref-with-digest }}
101
+
102
+ secrets :
103
+ checkout-token :
104
+ description : ' GitHub token for checkout'
105
+ required : false
106
+ registry-user :
107
+ required : false
108
+ registry-password :
109
+ required : false
110
+ build-secrets :
111
+ description : ' Build secrets as a multi-line string (e.g. "SECRET1=value1\nSECRET2=value2")'
112
+ required : false
113
+
114
+ permissions : {}
115
+
116
+ defaults :
117
+ run :
118
+ shell : bash -euo pipefail {0}
119
+
120
+ jobs :
121
+ build-image :
122
+ runs-on : ${{ matrix.runner }}
123
+
124
+ strategy :
125
+ fail-fast : false
126
+ matrix :
127
+ runner : ${{ fromJSON(inputs.build-runners) }}
128
+
129
+ permissions :
130
+ contents : read
131
+ packages : write
132
+
133
+ outputs :
134
+ digest_x86_64 : ${{ steps.digest.outputs.digest_x86_64 }}
135
+ digest_aarch64 : ${{ steps.digest.outputs.digest_aarch64 }}
136
+
137
+ steps :
138
+ - name : Checkout
139
+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
140
+ with :
141
+ submodules : ${{ inputs.checkout-submodules }}
142
+ ref : ${{ inputs.checkout-ref }}
143
+ token : ${{ secrets.checkout-token != '' && secrets.checkout-token || github.token }}
144
+ path : ${{ inputs.checkout-path }}
145
+
146
+ - name : Download artifact
147
+ if : ${{ inputs.artifact-name != '' }}
148
+ uses : actions/download-artifact@v4
149
+ with :
150
+ name : ${{ inputs.artifact-name }}
151
+ path : ${{ inputs.artifact-path }}
152
+
153
+ - name : Set up Docker Buildx
154
+ uses : docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
155
+ with :
156
+ cache-binary : false
157
+ buildkitd-config-inline : ${{ inputs.buildkit-config || '' }}
158
+
159
+ - name : Login to Container Registry
160
+ uses : docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
161
+ with :
162
+ registry : ${{ inputs.registry }}
163
+ username : ${{ secrets.registry-user != '' && secrets.registry-user || (inputs.registry == 'ghcr.io' && github.actor) }}
164
+ password : ${{ secrets.registry-password != '' && secrets.registry-password || (inputs.registry == 'ghcr.io' && github.token) }}
165
+
166
+ - name : Detect architecture
167
+ id : arch
168
+ run : |
169
+ arch=$(uname -m)
170
+ echo "arch=${arch}" | tee -a "$GITHUB_OUTPUT"
171
+
172
+ - name : Build and push architecture-specific image
173
+ id : build
174
+ env :
175
+ CACHE_KEY : ${{ inputs.cache-prefix && format('type=registry,ref={0}/{1}:{2}-{3}', inputs.registry, inputs.name, inputs.cache-prefix, steps.arch.outputs.arch) || '' }}
176
+ uses : docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
177
+ with :
178
+ context : ${{ inputs.context }}
179
+ file : ${{ inputs.dockerfile }}
180
+ push : true
181
+ pull : true
182
+ target : ${{ inputs.target }}
183
+ build-args : ${{ format('{0}\n{1}', inputs.build-args, fromJson(inputs.build-args-for-arch)[steps.arch.outputs.arch] || '') }}
184
+ secrets : ${{ secrets.build-secrets }}
185
+ cache-from : ${{ env.CACHE_KEY }}
186
+ cache-to : ${{ inputs.update-cache == 'true' && format('{0},image-manifest=true,oci-mediatypes=true,mode=max', env.CACHE_KEY) || '' }}
187
+ attests : |
188
+ type=provenance,mode=max
189
+ type=sbom,generator=${{ contains(inputs.registry, 'databricks.com') && format('{0}/brickstore/neon/docker/buildkit-syft-scanner:1', inputs.registry) || 'docker.io/docker/buildkit-syft-scanner:1' }}
190
+ outputs : type=registry,name=${{ inputs.registry }}/${{ inputs.name }},push-by-digest=true,name-canonical=true
191
+
192
+ - name : Export digest for architecture
193
+ id : digest
194
+ run : |
195
+ digest="${{ steps.build.outputs.digest }}"
196
+ echo "digest_$(uname -m)=${digest}" | tee -a "$GITHUB_OUTPUT"
197
+
198
+ merge :
199
+ runs-on : ${{ inputs.merge-runner }}
200
+ needs : [build-image]
201
+ if : always() && !cancelled() && !failure()
202
+
203
+ permissions :
204
+ contents : read
205
+ packages : write
206
+
207
+ env :
208
+ IMAGE_REF : ${{ inputs.registry }}/${{ inputs.name }}
209
+
210
+ outputs :
211
+ digest : ${{ steps.merge.outputs.digest }}
212
+ ref-with-digest : ${{ steps.merge.outputs.ref-with-digest }}
213
+
214
+ steps :
215
+ - name : Checkout
216
+ uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
217
+
218
+ - name : Set up Docker Buildx
219
+ uses : docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
220
+
221
+ - name : Login to Container Registry
222
+ uses : docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
223
+ with :
224
+ registry : ${{ inputs.registry }}
225
+ username : ${{ secrets.registry-user != '' && secrets.registry-user || (inputs.registry == 'ghcr.io' && github.actor) }}
226
+ password : ${{ secrets.registry-password != '' && secrets.registry-password || (inputs.registry == 'ghcr.io' && github.token) }}
227
+
228
+ - name : Prepare references
229
+ id : prepare
230
+ env :
231
+ DIGEST_X86_64 : ${{ needs.build-image.outputs.digest_x86_64 }}
232
+ DIGEST_AARCH64 : ${{ needs.build-image.outputs.digest_aarch64 }}
233
+ run : |
234
+ # Parse environment variables and create references in one go
235
+ references_data=$(printenv | jq -Rsc '[split("\n")[] | capture("^DIGEST_(?<arch>[^=]+)=(?<digest>.+)$") | .arch |= ascii_downcase]')
236
+
237
+ # Verify we have at least one digest
238
+ if [[ "$(echo "$references_data" | jq -r 'length')" -eq 0 ]]; then
239
+ echo "::error::No digest values found! Cannot create manifest list without at least one input reference."
240
+ exit 1
241
+ fi
242
+
243
+ # Log found architectures and their digests
244
+ echo "$references_data" | jq -r '.[] | "Found digest for \(.arch): \(.digest)"'
245
+
246
+ # Create space-separated references string for the composite action
247
+ references_string=$(echo "$references_data" | jq -r --arg ref "$IMAGE_REF" 'map($ref + "@" + .digest) | join(" ")')
248
+ echo "references=${references_string}" >> "$GITHUB_OUTPUT"
249
+
250
+ - name : Merge OCI manifest lists
251
+ id : merge
252
+ uses : ./merge-oci-manifest-lists
253
+ with :
254
+ target : ${{ inputs.registry }}/${{ inputs.name }}:${{ inputs.tag }}
255
+ references : ${{ steps.prepare.outputs.references }}
256
+
257
+ - name : Fetch manifest digest references
258
+ id : digests
259
+ uses : ./fetch-oci-manifest-list-digest-references
0 commit comments