Skip to content

Commit 1bda9e2

Browse files
authored
Merge pull request #7898 from ecamp/optimize-docker-build
Optimize docker build
2 parents 96ddba4 + 96c3a34 commit 1bda9e2

File tree

5 files changed

+255
-70
lines changed

5 files changed

+255
-70
lines changed

.docker-hub/docker-compose.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
services:
2+
frontend-image:
3+
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-frontend:${VERSION:-latest}
4+
build:
5+
context: ../
6+
dockerfile: .docker-hub/frontend/Dockerfile
7+
# they have to be registered in GitHub under exactly this name in secrets or vars
8+
args:
9+
SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN:-}
10+
SENTRY_ORG: ${SENTRY_ORG:-}
11+
SENTRY_FRONTEND_PROJECT: ${SENTRY_FRONTEND_PROJECT:-}
12+
SENTRY_RELEASE_NAME: ${RELEASE_NAME:-}
13+
print-image:
14+
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-print:${VERSION:-latest}
15+
build:
16+
context: ../
17+
dockerfile: .docker-hub/print/Dockerfile
18+
# they have to be registered in GitHub under exactly this name in secrets or vars
19+
args:
20+
SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN:-}
21+
SENTRY_ORG: ${SENTRY_ORG:-}
22+
SENTRY_PRINT_PROJECT: ${SENTRY_PRINT_PROJECT:-}
23+
SENTRY_RELEASE_NAME: ${RELEASE_NAME:-}
24+
varnish-image:
25+
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-varnish:${VERSION:-latest}
26+
build:
27+
context: ../
28+
dockerfile: .docker-hub/varnish/Dockerfile
Lines changed: 224 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
name: '[reusable only] Build images and push to registry'
1+
name: "[reusable only] Build images and push to registry"
22

33
on:
4+
workflow_dispatch:
45
workflow_call:
56
inputs:
67
tag:
@@ -17,91 +18,246 @@ on:
1718
required: true
1819
SENTRY_AUTH_TOKEN:
1920

21+
env:
22+
DOCKER_BUILDKIT: 1
23+
COMPOSE_DOCKER_CLI_BUILD: 1
24+
2025
jobs:
26+
build-info:
27+
runs-on: ubuntu-latest
28+
outputs:
29+
repo-owner: ${{ steps.repo-owner.outputs.result }}
30+
tags: ${{ steps.image-tags.outputs.image-tags }}
31+
build-config: ${{ steps.build-info.outputs.result }}
32+
steps:
33+
#github forces lower case for the image name
34+
- name: Get lowercase repo owner name
35+
uses: actions/github-script@v7
36+
id: repo-owner
37+
with:
38+
result-encoding: string
39+
script: |
40+
return context.repo.owner.toLowerCase()
41+
42+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
43+
44+
- name: Set nightly tag if commit was on main
45+
id: add-nightly-tag
46+
if: startsWith(github.ref, 'refs/heads/devel')
47+
run: |
48+
echo "nightly-tag=nightly" | tr -d "\n" >> $GITHUB_OUTPUT
49+
50+
- name: Set latest tag if its a tag
51+
id: add-latest-tag
52+
if: startsWith(github.ref, 'refs/tags/')
53+
run: |
54+
echo "latest-tag=latest" | tr -d "\n" >> $GITHUB_OUTPUT
55+
56+
- uses: actions/github-script@v7
57+
id: get-tag
58+
if: startsWith(github.ref, 'refs/tags/')
59+
with:
60+
result-encoding: string
61+
script: |
62+
return context.payload.ref.replace('refs/tags/', '')
63+
64+
- name: concat tags to list
65+
id: image-tags
66+
run: |
67+
TAGS=$(cat <<-END
68+
[
69+
"${{ inputs.sha || github.sha }}",
70+
"${{ steps.add-nightly-tag.outputs.nightly-tag }}",
71+
"${{ steps.add-latest-tag.outputs.latest-tag }}",
72+
"${{ steps.get-tag.outputs.result }}"
73+
]
74+
END
75+
)
76+
TAGS=$(echo $TAGS | jq -c 'map(select(length > 0))')
77+
echo "image-tags=$TAGS" | tr -d "\n" >> $GITHUB_OUTPUT
78+
79+
- name: Get build info
80+
id: build-info
81+
run: |
82+
set -x
83+
sudo snap install yq
84+
85+
export REPO_NAME=$(basename $(pwd))
86+
echo "services:" > /tmp/docker-compose.yml
87+
for i in $(find . -name docker-compose.yml); do
88+
docker compose -f $i config | yq '.services | select(.[].build != null and .[].image != null)' | sed 's/^/ /' >> /tmp/docker-compose.yml
89+
done
90+
91+
cat /tmp/docker-compose.yml
92+
93+
yq_pipe='.services'
94+
yq_pipe=$yq_pipe'| to_entries[]'
95+
yq_pipe=$yq_pipe'| select(.value.build != null and .value.image != null)'
96+
yq_pipe=$yq_pipe'| .value.build.image=.value.image'
97+
yq_pipe=$yq_pipe'| .value.build.service=.key'
98+
yq_pipe=$yq_pipe'| .value.build'
99+
yq_pipe=$yq_pipe'| .dockerfile=.context + "/" + .dockerfile'
100+
yq_pipe=$yq_pipe'| [.]'
101+
cat /tmp/docker-compose.yml | yq "$yq_pipe"
102+
BUILD_INFO=$(cat /tmp/docker-compose.yml | yq "$yq_pipe" -o=json)
103+
BUILD_INFO=$(echo $BUILD_INFO | jq -c -s add | sed "s|:local||g")
104+
echo $BUILD_INFO
105+
echo "result=$BUILD_INFO" | tr -d "\n" >> $GITHUB_OUTPUT
106+
env:
107+
REPO_OWNER: ${{ vars.DOCKER_HUB_USERNAME || steps.repo-owner.outputs.result }}
108+
VERSION: local
109+
21110
build-and-push:
22-
name: Build images and push
23111
runs-on: ubuntu-latest
112+
name: "Build and push image ${{ matrix.build-config.service }}"
113+
needs:
114+
- build-info
115+
strategy:
116+
fail-fast: false
117+
matrix:
118+
build-config: ${{ fromJSON(needs.build-info.outputs.build-config) }}
119+
env:
120+
tags: ${{ needs.build-info.outputs.tags }}
121+
repo-owner: ${{ needs.build-info.outputs.repo-owner }}
24122
steps:
25123
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
26124
with:
27-
ref: ${{ inputs.sha }}
125+
fetch-depth: 100
126+
127+
- run: |
128+
echo "inputs:"
129+
cat <<-HEREDOC
130+
${{ toJSON(inputs) }}
131+
HEREDOC
132+
133+
echo "build config:"
134+
cat <<-HEREDOC
135+
${{ toJSON(matrix.build-config) }}
136+
HEREDOC
137+
138+
echo "build tags:"
139+
cat <<-HEREDOC
140+
${{ env.tags }}
141+
HEREDOC
142+
143+
echo "build repo owner:"
144+
cat <<-HEREDOC
145+
${{ env.repo-owner }}
146+
HEREDOC
147+
148+
if [ ! echo "${{ matrix.build-config.image }}" | grep -Eq '^[a-zA-Z0-9._/-]+$' ]; then
149+
echo "Error: Invalid Docker image name '${{ matrix.build-config.image }}'."
150+
exit 1
151+
fi
28152
29153
- name: Set up Docker Buildx
30154
uses: docker/setup-buildx-action@v3
31155

32156
- name: Login to DockerHub
33157
uses: docker/login-action@v3
34158
with:
35-
username: ${{ vars.DOCKER_HUB_USERNAME }}
159+
username: ${{ vars.DOCKER_HUB_USERNAME || env.repo-owner }}
36160
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
37161

38-
- name: Build and push frontend docker image
39-
uses: docker/build-push-action@v6
40-
with:
41-
push: true
42-
file: .docker-hub/frontend/Dockerfile
43-
tags: |
44-
${{ ((inputs.tag != '') && format('{0}/ecamp3-frontend:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }}
45-
${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-frontend:${{ inputs.sha }}
46-
context: .
47-
build-args: |
48-
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
49-
SENTRY_ORG=${{ vars.SENTRY_ORG }}
50-
SENTRY_FRONTEND_PROJECT=${{ vars.SENTRY_FRONTEND_PROJECT }}
51-
SENTRY_RELEASE_NAME=${{ inputs.sha }}
52-
cache-from: type=gha,scope=frontend
53-
cache-to: type=gha,scope=frontend,mode=max
54-
55-
- name: Build and push api docker image
56-
uses: docker/build-push-action@v6
162+
- name: find latest commit for image tag
163+
id: get-latest-commit
164+
run: |
165+
set -x
166+
context=$( echo '${{ toJSON(matrix.build-config) }}' | jq -r '.context' | sed "s|$PWD|.|g")
167+
echo "context: $context"
168+
dockerfile=$( echo '${{ toJSON(matrix.build-config) }}' | jq -r '.dockerfile' | sed "s|$PWD|.|g")
169+
echo "dockerfile: $dockerfile"
170+
git log --stat -n 1 $context $dockerfile
171+
latest_commit=$(git log --pretty=format:"%H" -n 1 $context $dockerfile)
172+
echo "latest_commit: $latest_commit"
173+
echo "result=$latest_commit" | tr -d "\n" >> $GITHUB_OUTPUT
174+
175+
- name: check if image already exists
176+
id: check-image
177+
run: |
178+
set +e
179+
docker pull ${{ matrix.build-config.image }}:${{ steps.get-latest-commit.outputs.result }}
180+
image_exists=$?
181+
set -e
182+
if ([ $image_exists -eq 0 ]); then
183+
echo "image already exists"
184+
echo "result=true" | tr -d "\n" >> $GITHUB_OUTPUT
185+
else
186+
echo "image does not exist"
187+
echo "result=false" | tr -d "\n" >> $GITHUB_OUTPUT
188+
fi
189+
190+
- name: Add latest commit to image tag if image does not exist
191+
id: add-latest-commit-to-tags-if-image-does-not-exist
192+
uses: actions/github-script@v7
57193
with:
58-
push: true
59-
file: api/Dockerfile
60-
tags: |
61-
${{ ((inputs.tag != '') && format('{0}/ecamp3-api:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }}
62-
${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-api:${{ inputs.sha }}
63-
context: './api'
64-
target: frankenphp_prod
65-
cache-from: type=gha,scope=api
66-
cache-to: type=gha,scope=api,mode=max
67-
68-
- name: Build and push print docker image
69-
uses: docker/build-push-action@v6
194+
script: |
195+
if (!${{ steps.check-image.outputs.result }}) {
196+
return [...JSON.parse('${{ env.tags }}'), '${{ steps.get-latest-commit.outputs.result }}']
197+
}
198+
return JSON.parse('${{ env.tags }}')
199+
200+
- uses: actions/github-script@v7
201+
id: expand-tags
70202
with:
71-
push: true
72-
file: .docker-hub/print/Dockerfile
73-
tags: |
74-
${{ ((inputs.tag != '') && format('{0}/ecamp3-print:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }}
75-
${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-print:${{ inputs.sha }}
76-
context: .
77-
build-args: |
78-
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
79-
SENTRY_ORG=${{ vars.SENTRY_ORG }}
80-
SENTRY_PRINT_PROJECT=${{ vars.SENTRY_PRINT_PROJECT }}
81-
SENTRY_RELEASE_NAME=${{ inputs.sha }}
82-
cache-from: type=gha,scope=print
83-
cache-to: type=gha,scope=print,mode=max
84-
85-
- name: Build and push varnish docker image
86-
uses: docker/build-push-action@v6
203+
script: |
204+
return JSON.parse('${{ steps.add-latest-commit-to-tags-if-image-does-not-exist.outputs.result }}').map(tag => `${{ matrix.build-config.image }}:${ tag }`)
205+
206+
- name: populate build args from secrets/vars
207+
if: steps.check-image.outputs.result == 'false'
208+
id: populate-build-args
209+
uses: actions/github-script@v7
87210
with:
88-
push: true
89-
file: .docker-hub/varnish/Dockerfile
90-
tags: |
91-
${{ ((inputs.tag != '') && format('{0}/ecamp3-varnish:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }}
92-
${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-varnish:${{ inputs.sha }}
93-
context: .
94-
cache-from: type=gha,scope=varnish
95-
cache-to: type=gha,scope=varnish,mode=max
96-
97-
- name: Build and push db-backup-restore docker image
211+
script: |
212+
const buildArgValues = {
213+
"SENTRY_AUTH_TOKEN" : "${{ secrets.SENTRY_AUTH_TOKEN }}",
214+
"SENTRY_FRONTEND_PROJECT" : "${{ vars.SENTRY_FRONTEND_PROJECT }}",
215+
"SENTRY_ORG" : "${{ vars.SENTRY_ORG }}",
216+
"SENTRY_PRINT_PROJECT" : "${{ vars.SENTRY_PRINT_PROJECT }}",
217+
"SENTRY_RELEASE_NAME" : "${{ inputs.sha }}",
218+
}
219+
const args = JSON.parse(`${{ toJSON(matrix.build-config.args) }}`)
220+
let result = ""
221+
for (const arg in args) {
222+
if (buildArgValues[arg]) {
223+
args[arg] = buildArgValues[arg]
224+
}
225+
}
226+
return args
227+
228+
- name: transform build args
229+
if: steps.check-image.outputs.result == 'false'
230+
id: transform-build-args
231+
run: |
232+
set -x
233+
echo 'result<<EOF' >> $GITHUB_OUTPUT
234+
echo '${{ steps.populate-build-args.outputs.result }}' | yq -p json >> $GITHUB_OUTPUT
235+
echo 'EOF' >> $GITHUB_OUTPUT
236+
cat $GITHUB_OUTPUT
237+
238+
- name: debug transform build args output
239+
if: steps.check-image.outputs.result == 'false'
240+
run: |
241+
echo "result: ${{ steps.transform-build-args.outputs.result }}"
242+
echo "result: ${{ toJSON(steps.transform-build-args.outputs) }}"
243+
244+
- name: Build and push image
98245
uses: docker/build-push-action@v6
246+
if: steps.check-image.outputs.result == 'false'
99247
with:
100248
push: true
101-
file: .helm/ecamp3/files/db-backup-restore-image/Dockerfile
102-
tags: |
103-
${{ ((inputs.tag != '') && format('{0}/ecamp3-db-backup-restore:{1}', vars.DOCKER_HUB_USERNAME, inputs.tag) || '') }}
104-
${{ vars.DOCKER_HUB_USERNAME }}/ecamp3-db-backup-restore:${{ inputs.sha }}
105-
context: .
106-
cache-from: type=gha,scope=db-backup-restore
107-
cache-to: type=gha,scope=db-backup-restore,mode=max
249+
file: ${{ matrix.build-config.dockerfile }}
250+
tags: ${{ join(fromJSON(steps.expand-tags.outputs.result)) }}
251+
context: ${{ matrix.build-config.context }}
252+
build-args: |
253+
${{ steps.transform-build-args.outputs.result }}
254+
cache-from: type=gha,scope=${{ matrix.build-config.image }}
255+
cache-to: type=gha,scope=${{ matrix.build-config.image }},mode=max
256+
257+
- name: Retag and push images
258+
if: steps.check-image.outputs.result == 'true'
259+
run: |
260+
for tag in $(echo '${{ env.tags }}' | jq -r '.[]'); do
261+
docker tag ${{ matrix.build-config.image }}:${{ steps.get-latest-commit.outputs.result }} ${{ matrix.build-config.image }}:${tag}
262+
docker push ${{ matrix.build-config.image }}:${tag}
263+
done

.github/workflows/reusable-e2e-tests-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
load: true
2626
target: frankenphp_prod
2727
builder: ${{ steps.buildx.outputs.name }}
28-
tags: ecamp/ecamp3-dev-api
28+
tags: docker.io/ecamp/ecamp3-api:latest
2929
cache-from: type=gha,scope=api
3030
cache-to: type=gha,scope=api,mode=max
3131
outputs: type=docker,dest=/tmp/ecamp3-dev-api.tar

docker-compose.override.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
services:
22
api:
3+
image: ''
34
build:
45
target: frankenphp_dev
56
volumes:

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ services:
2525
- pdf
2626

2727
api:
28-
image: ecamp/ecamp3-dev-api
28+
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-api:${VERSION:-latest}
2929
build:
3030
context: ./api
3131
target: frankenphp_prod

0 commit comments

Comments
 (0)