Skip to content

Commit db83393

Browse files
authored
feat: CI/CD speed-up and cache improvements (#23)
* refactor: enhance Dockerfile build flow - Re-arranged steps order to install requirements before copying backend for better caching. - Introduced a healthcheck command to monitor the application status. - Set environment variables and documented the exposed port for better container management. * feat: implement multi-architecture Docker build workflow - Added separate jobs for building and pushing AMD64 and ARM64 Docker images. - Configured conditional execution for ARM64 builds to only run on the main branch. - Enhanced the Docker build process with digest export and artifact upload for both architectures. - Created a job to generate a multi-architecture manifest after successful builds. - Updated tags and labels for better image management.
1 parent bc68c59 commit db83393

File tree

2 files changed

+193
-7
lines changed

2 files changed

+193
-7
lines changed

.github/workflows/docker-build.yml

Lines changed: 160 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@ env:
1212
IMAGE_NAME: ${{ github.repository }}
1313

1414
jobs:
15-
build-and-push:
15+
# Job for AMD64 architecture - runs on all events
16+
build-amd64:
1617
runs-on: ubuntu-latest
1718
permissions:
1819
contents: read
1920
packages: write
20-
2121
steps:
2222
- name: Checkout repository
2323
uses: actions/checkout@v4
2424

2525
- name: Set up Docker Buildx
2626
uses: docker/setup-buildx-action@v3
27+
id: buildx
2728

2829
- name: Log in to the Container registry
30+
if: github.event_name != 'pull_request'
2931
uses: docker/login-action@v3
3032
with:
3133
registry: ${{ env.REGISTRY }}
@@ -44,14 +46,167 @@ jobs:
4446
type=semver,pattern={{major}}.{{minor}}
4547
type=sha,format=long
4648
47-
- name: Build and push Docker image
49+
- name: Build and push AMD64 Docker image
50+
id: build
4851
uses: docker/build-push-action@v5
4952
with:
5053
context: .
5154
push: ${{ github.event_name != 'pull_request' }}
52-
tags: ${{ steps.meta.outputs.tags }}
55+
tags: ${{ steps.meta.outputs.tags }}-amd64
56+
labels: ${{ steps.meta.outputs.labels }}
57+
platforms: linux/amd64
58+
cache-from: type=gha
59+
cache-to: type=gha,mode=max
60+
builder: ${{ steps.buildx.outputs.name }}
61+
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
62+
63+
- name: Export AMD64 digest
64+
if: github.event_name != 'pull_request'
65+
run: |
66+
mkdir -p /tmp/digests
67+
digest="${{ steps.build.outputs.digest }}"
68+
touch "/tmp/digests/${digest#sha256:}"
69+
echo "AMD64_DIGEST=${digest}" >> $GITHUB_ENV
70+
71+
- name: Upload AMD64 digest
72+
if: github.event_name != 'pull_request'
73+
uses: actions/upload-artifact@v4
74+
with:
75+
name: amd64-digest
76+
path: /tmp/digests/*
77+
if-no-files-found: error
78+
retention-days: 1
79+
80+
# Job for ARM64 architecture - only runs on main branch
81+
build-arm64:
82+
runs-on: ubuntu-latest
83+
permissions:
84+
contents: read
85+
packages: write
86+
# Only run this job for pushes to main, not for PRs
87+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
88+
steps:
89+
- name: Checkout repository
90+
uses: actions/checkout@v4
91+
92+
- name: Set up Docker Buildx
93+
uses: docker/setup-buildx-action@v3
94+
id: buildx
95+
96+
- name: Log in to the Container registry
97+
uses: docker/login-action@v3
98+
with:
99+
registry: ${{ env.REGISTRY }}
100+
username: ${{ github.actor }}
101+
password: ${{ secrets.GITHUB_TOKEN }}
102+
103+
- name: Extract metadata (tags, labels) for Docker
104+
id: meta
105+
uses: docker/metadata-action@v5
106+
with:
107+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
108+
tags: |
109+
type=ref,event=branch
110+
type=semver,pattern={{version}}
111+
type=semver,pattern={{major}}.{{minor}}
112+
type=sha,format=long
113+
114+
- name: Build and push ARM64 Docker image
115+
id: build
116+
uses: docker/build-push-action@v5
117+
with:
118+
context: .
119+
push: true
120+
tags: ${{ steps.meta.outputs.tags }}-arm64
53121
labels: ${{ steps.meta.outputs.labels }}
54-
platforms: linux/amd64,linux/arm64
122+
platforms: linux/arm64
55123
cache-from: type=gha
56124
cache-to: type=gha,mode=max
57125
builder: ${{ steps.buildx.outputs.name }}
126+
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
127+
128+
- name: Export ARM64 digest
129+
run: |
130+
mkdir -p /tmp/digests
131+
digest="${{ steps.build.outputs.digest }}"
132+
touch "/tmp/digests/${digest#sha256:}"
133+
echo "ARM64_DIGEST=${digest}" >> $GITHUB_ENV
134+
135+
- name: Upload ARM64 digest
136+
uses: actions/upload-artifact@v4
137+
with:
138+
name: arm64-digest
139+
path: /tmp/digests/*
140+
if-no-files-found: error
141+
retention-days: 1
142+
143+
# Job to create multi-architecture manifest
144+
create-manifest:
145+
runs-on: ubuntu-latest
146+
needs: [build-amd64, build-arm64]
147+
# This job only runs if at least build-amd64 completed successfully
148+
# build-arm64 might be skipped for PRs, so we don't require it
149+
if: github.event_name != 'pull_request' && always() && needs.build-amd64.result == 'success'
150+
permissions:
151+
contents: read
152+
packages: write
153+
steps:
154+
- name: Download AMD64 digest
155+
uses: actions/download-artifact@v4
156+
with:
157+
name: amd64-digest
158+
path: /tmp/digests/amd64
159+
160+
- name: Download ARM64 digest
161+
# Only try to download ARM64 digest if the job ran
162+
if: needs.build-arm64.result == 'success'
163+
uses: actions/download-artifact@v4
164+
with:
165+
name: arm64-digest
166+
path: /tmp/digests/arm64
167+
168+
- name: Set up Docker Buildx
169+
uses: docker/setup-buildx-action@v3
170+
171+
- name: Log in to the Container registry
172+
uses: docker/login-action@v3
173+
with:
174+
registry: ${{ env.REGISTRY }}
175+
username: ${{ github.actor }}
176+
password: ${{ secrets.GITHUB_TOKEN }}
177+
178+
- name: Extract metadata (tags, labels) for Docker
179+
id: meta
180+
uses: docker/metadata-action@v5
181+
with:
182+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
183+
tags: |
184+
type=ref,event=branch
185+
type=semver,pattern={{version}}
186+
type=semver,pattern={{major}}.{{minor}}
187+
type=sha,format=long
188+
189+
- name: Create manifest list and push
190+
run: |
191+
# Get the first tag from meta outputs
192+
FIRST_TAG=$(echo "${{ steps.meta.outputs.tags }}" | cut -d, -f1)
193+
194+
# If ARM64 build was skipped, only use AMD64 digest
195+
if [ "${{ needs.build-arm64.result }}" != "success" ]; then
196+
AMD64_DIGEST=$(cat /tmp/digests/amd64/*)
197+
docker buildx imagetools create \
198+
--tag ${FIRST_TAG} \
199+
${AMD64_DIGEST}
200+
else
201+
# Otherwise use both digests
202+
AMD64_DIGEST=$(cat /tmp/digests/amd64/*)
203+
ARM64_DIGEST=$(cat /tmp/digests/arm64/*)
204+
docker buildx imagetools create \
205+
--tag ${FIRST_TAG} \
206+
${AMD64_DIGEST} ${ARM64_DIGEST}
207+
fi
208+
209+
- name: Inspect image
210+
run: |
211+
FIRST_TAG=$(echo "${{ steps.meta.outputs.tags }}" | cut -d, -f1)
212+
docker buildx imagetools inspect ${FIRST_TAG}

Dockerfile

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
1+
# Frontend build stage
12
FROM node:20-slim as frontend-builder
23
WORKDIR /app/frontend
4+
5+
# Copy package files first to leverage layer caching
36
COPY src/frontend/package.json src/frontend/yarn.lock ./
47
RUN yarn install --frozen-lockfile
8+
9+
# Copy all frontend files
510
COPY src/frontend/ ./
11+
12+
# Build the frontend
613
RUN yarn build
714

15+
# Backend stage
816
FROM python:3.11-slim
917
WORKDIR /app
10-
COPY src/backend /app
18+
19+
# Copy requirements first to leverage layer caching
20+
COPY src/backend/requirements.txt .
1121
RUN pip install --no-cache-dir -r requirements.txt
22+
23+
# Install curl for healthcheck
24+
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
25+
apt-get clean && \
26+
rm -rf /var/lib/apt/lists/*
27+
28+
# Copy backend files
29+
COPY src/backend .
30+
31+
# Copy built frontend from the frontend-builder stage
1232
COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
1333

14-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
34+
# Add healthcheck
35+
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
36+
CMD curl -f http://localhost:8000/ || exit 1
37+
38+
# Set environment variables
39+
ENV PYTHONUNBUFFERED=1
40+
41+
# Document the port number the container will expose
42+
EXPOSE 8000
43+
44+
# Run the application
45+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

0 commit comments

Comments
 (0)