Skip to content

Commit 9235f95

Browse files
committed
refactor: Utilize multistage builds to improve caching and compatibility
The main changes here are; * using multi-stage builds for better caching and smaller images * explicitly listing the files that should be included / "COPY"able into the containers For the API, I moved the tests outside the `src` folder. That way, we do not include the tests in the finished image. Since we use multi-stage builds, we can install the necessary compilers for running on ARM. The compilers are not part of the "production" image, and we don't need to worry about them taking up more space in the image layers. I also created a user (similar to what's done in `nginx`) so that we can run the tests without running the container as root. When only installing the necessary packages, I noticed that `click` is not explicitly defined, so it wasn't included in `site-packages`. By explicitly listing it in `pyproject.toml`, that issue is resolved.
1 parent df215da commit 9235f95

32 files changed

+202
-108
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ repos:
2626
- id: mixed-line-ending
2727
exclude: ^.*\.(lock)$
2828
- id: detect-private-key
29-
exclude: api/src/tests/integration/mock_authentication.py
29+
exclude: api/tests/integration/mock_authentication.py
3030
- id: no-commit-to-branch
3131
args: [--branch, main, --branch, master]
3232
stages: [commit-msg]
@@ -80,7 +80,7 @@ repos:
8080
hooks:
8181
- id: pytest
8282
name: pytest-check
83-
entry: sh -c "cd ./api/src/ && poetry run pytest ./tests/"
83+
entry: sh -c "cd ./api/ && poetry run pytest ./tests/"
8484
language: system
8585
pass_filenames: false
8686
always_run: true

api/.dockerignore

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1-
.venv
2-
.pytest_cache
3-
.mypy_cache
1+
# Ignore all by default
2+
*
3+
!pyproject.toml
4+
!poetry.lock
5+
6+
!src/**/*.py
7+
!src/*.py
8+
!src/init.sh
9+
10+
!tests/**/*.py
11+
!tests/test_data/

api/Dockerfile

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,58 @@
1-
FROM --platform=linux/amd64 python:3.12-slim AS base
1+
# syntax=docker/dockerfile:1
2+
FROM python:3.13.2-alpine3.21 AS base
3+
ENV XDG_CACHE_HOME=/var/lib/
4+
ENV SITE_PACKAGES=/usr/local/lib/python3.13/site-packages
5+
ENV PYTHONPATH=/code/src
6+
7+
ENV USER="api-user"
8+
9+
RUN adduser \
10+
--disabled-password \
11+
--home /code \
12+
--gecos "" \
13+
--uid 1000 \
14+
"$USER"
15+
216
WORKDIR /code
317
CMD ["/code/src/init.sh", "api"]
418
EXPOSE 5000
519

620
ENV PYTHONUNBUFFERED=1
7-
ENV PYTHONPATH=/code
21+
ENV PYTHONPATH=/code/src
822

9-
RUN pip install --upgrade pip && \
10-
pip install poetry && \
23+
FROM base AS dependencies
24+
ENV POETRY_VERSION=1.8.5
25+
26+
ENV PATH="/root/.local/bin:$PATH"
27+
RUN \
28+
--mount=type=cache,target="$XDG_CACHE_HOME/pip" \
29+
pip install --upgrade pip && \
30+
pip install --user "poetry==$POETRY_VERSION" && \
1131
poetry config virtualenvs.create false
1232

13-
COPY pyproject.toml pyproject.toml
14-
COPY poetry.lock poetry.lock
33+
RUN --mount=type=cache,target=/var/cache/apk \
34+
apk add \
35+
linux-headers \
36+
musl-dev \
37+
gcc
38+
39+
COPY --chown="$USER:$USER" \
40+
pyproject.toml poetry.lock ./
41+
RUN --mount=type=cache,target="$XDG_CACHE_HOME/pip" \
42+
poetry install
43+
44+
FROM dependencies AS dependencies-slim
45+
RUN rm -rf \
46+
"$SITE_PACKAGES/pip-*" \
47+
"$SITE_PACKAGES/README.txt"
1548

16-
FROM base AS development
17-
RUN poetry install
18-
WORKDIR /code/src
19-
COPY src .
49+
FROM dependencies AS development
50+
RUN --mount=type=cache,target="$XDG_CACHE_HOME/pip" \
51+
poetry install --with dev
52+
COPY --chown="$USER:$USER" . .
2053
USER 1000
2154

2255
FROM base AS prod
23-
RUN poetry install --without dev
24-
WORKDIR /code/src
25-
COPY src .
56+
COPY --from=dependencies-slim "$SITE_PACKAGES" "$SITE_PACKAGES"
57+
COPY --chown="$USER:$USER" src src
2658
USER 1000

api/poetry.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/pyproject.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pydantic-extra-types = "^2.10"
2020
azure-monitor-opentelemetry = "^1.6.2"
2121
opentelemetry-instrumentation-fastapi = "^0.48b0"
2222
cryptography = "^44.0.0"
23+
click = "^8.1.8"
2324

2425
[tool.poetry.group.dev.dependencies]
2526
pre-commit = ">=3"
@@ -65,7 +66,7 @@ strict = true
6566

6667
[tool.ruff]
6768

68-
src = ["src"]
69+
src = [".", "src"]
6970
target-version = "py311"
7071
line-length = 119
7172

@@ -87,7 +88,7 @@ ignore = [
8788

8889
[tool.ruff.lint.per-file-ignores]
8990
"__init__.py" = ["E402"] # Ignore `E402` (import violations) in all `__init__.py` files
90-
"src/tests/*" = ["S101"] # Allow the use of ´assert´ in tests
91+
"tests/*" = ["S101"] # Allow the use of ´assert´ in tests
9192

9293
[tool.codespell]
9394
skip = "*.lock,*.cjs"
@@ -100,6 +101,9 @@ markers = [
100101
"integration: mark a test as integration test."
101102
]
102103
testpaths = [
103-
"src/tests/unit",
104-
"src/tests/integration"
104+
"tests/unit",
105+
"tests/integration"
106+
]
107+
pythonpath = [
108+
"src"
105109
]

api/src/init.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
#!/bin/sh
1+
#!/usr/bin/env sh
22
set -eu
33

4+
ROOT_DIR="$(cd "$(dirname -- "$0")" && pwd -P)"
5+
readonly ROOT_DIR
6+
47
if [ "$1" = 'api' ]; then
5-
python3 ./app.py run
8+
python3 "$ROOT_DIR/app.py" run
69
else
710
exec "$@"
811
fi
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)