Skip to content

Commit 95965cc

Browse files
2 parents 9f98da1 + 2bd35d0 commit 95965cc

File tree

4 files changed

+283
-116
lines changed

4 files changed

+283
-116
lines changed
Lines changed: 51 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,88 @@
1-
# syntax=docker/dockerfile:1.7.0
1+
# syntax=docker/dockerfile:1.7.1
22

3-
# full semver just for python base image
4-
ARG PYTHON_VERSION=3.11.11
3+
ARG PYTHON_VERSION=3.12.10
54

6-
FROM python:${PYTHON_VERSION}-slim-bullseye AS builder
5+
FROM python:${PYTHON_VERSION}-slim-bookworm as builder
76

8-
# avoid stuck build due to user prompt
97
ARG DEBIAN_FRONTEND=noninteractive
108

11-
# update apt-get repos and install dependencies
12-
RUN apt-get -qq update \
9+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
10+
--mount=type=cache,target=/var/lib/apt,sharing=locked \
11+
apt-get -qq update \
1312
&& apt-get -qq install --no-install-recommends -y \
13+
build-essential \
14+
ca-certificates \
1415
curl \
1516
gcc \
16-
libpq-dev \
1717
python3-dev \
1818
&& rm -rf /var/lib/apt/lists/*
1919

20-
# pip env vars
21-
ENV PIP_NO_CACHE_DIR=off
22-
ENV PIP_DISABLE_PIP_VERSION_CHECK=on
23-
ENV PIP_DEFAULT_TIMEOUT=100
20+
# venv
21+
ARG UV_PROJECT_ENVIRONMENT="/opt/venv"
22+
ENV VENV="${UV_PROJECT_ENVIRONMENT}"
23+
ENV PATH="$VENV/bin:$PATH"
2424

25-
# poetry env vars
26-
ENV POETRY_HOME="/opt/poetry"
27-
ENV POETRY_VERSION=1.8.5
28-
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
29-
ENV POETRY_NO_INTERACTION=1
25+
# uv
26+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
3027

31-
# path
32-
ENV VENV="/opt/venv"
33-
ENV PATH="$POETRY_HOME/bin:$VENV/bin:$PATH"
28+
ARG WORKDIR="/app"
29+
WORKDIR $WORKDIR
3430

35-
# create app directory and set as working directory
36-
WORKDIR /app
31+
COPY pyproject.toml .
3732

38-
# copy dependencies
39-
COPY requirements.txt requirements.txt
33+
# optimize startup time, don't use hardlinks, set cache for buildkit mount,
34+
# set uv timeout
35+
ENV UV_COMPILE_BYTECODE=1
36+
ENV UV_LINK_MODE=copy
37+
ENV UV_CACHE_DIR=/opt/uv-cache/
38+
ENV UV_HTTP_TIMEOUT=90
4039

41-
# install poetry and dependencies
42-
RUN python -m venv $VENV \
43-
&& . "${VENV}/bin/activate" \
44-
&& python -m pip install "poetry==${POETRY_VERSION}" \
45-
&& python -m pip install -r requirements.txt
40+
RUN --mount=type=cache,target=/opt/uv-cache,sharing=locked \
41+
uv venv $UV_PROJECT_ENVIRONMENT \
42+
&& uv pip install -r pyproject.toml
4643

47-
FROM python:${PYTHON_VERSION}-slim-bullseye AS dev
44+
FROM python:${PYTHON_VERSION}-slim-bookworm as deps
4845

49-
# setup path
50-
ENV VENV="/opt/venv"
51-
ENV PATH="${VENV}/bin:${VENV}/lib/python${PYTHON_VERSION}/site-packages:/usr/local/bin:${HOME}/.local/bin:/bin:/usr/bin:/usr/share/doc:$PATH"
52-
53-
# standardise on locale, don't generate .pyc, enable tracebacks on seg faults
54-
ENV LANG C.UTF-8
55-
ENV LC_ALL C.UTF-8
56-
ENV PYTHONDONTWRITEBYTECODE 1
57-
ENV PYTHONFAULTHANDLER 1
58-
59-
# workers per core
60-
# https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/README.md#web_concurrency
61-
ENV WEB_CONCURRENCY=2
62-
63-
# avoid stuck build due to user prompt
6446
ARG DEBIAN_FRONTEND=noninteractive
6547

66-
# install dependencies
67-
RUN apt-get -qq update \
48+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
49+
--mount=type=cache,target=/var/lib/apt,sharing=locked \
50+
apt-get -qq update \
6851
&& apt-get -qq install --no-install-recommends -y \
69-
bat \
70-
curl \
71-
dpkg \
72-
git \
73-
iputils-ping \
74-
lsof \
75-
make \
76-
p7zip \
77-
perl \
78-
shellcheck \
79-
sudo \
80-
tldr \
81-
tree \
52+
libgomp1 \
8253
&& rm -rf /var/lib/apt/lists/*
8354

84-
# setup standard non-root user for use downstream
55+
FROM deps as runner
56+
57+
ARG WORKDIR="/app"
58+
WORKDIR $WORKDIR
59+
8560
ARG USER_NAME=appuser
8661
ARG USER_UID=1000
8762
ARG USER_GID=$USER_UID
8863

8964
RUN groupadd --gid $USER_GID $USER_NAME \
90-
&& useradd --uid $USER_UID --gid $USER_GID -m $USER_NAME
91-
92-
RUN echo "$USER_NAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER_NAME \
93-
&& chmod 0440 /etc/sudoers.d/$USER_NAME
65+
&& useradd --uid $USER_UID --gid $USER_GID -m $USER_NAME \
66+
&& chown -R $USER_NAME:$USER_NAME /app
9467

95-
# copy virtual environment from builder stage
96-
COPY --from=builder --chown=${USER_NAME}:${USER_GROUP} $VENV $VENV
97-
98-
# qol: tooling
99-
RUN <<EOF
100-
#!/usr/bin/env bash
101-
# fzf
102-
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
103-
yes | ~/.fzf/install
104-
EOF
105-
106-
# switch to non-root user
107-
USER $USER_NAME
68+
ARG VENV="/opt/venv"
69+
ENV PATH=$VENV/bin:$HOME/.local/bin:$PATH
10870

109-
# qol: .bashrc
110-
RUN tee -a "$HOME/.bashrc" <<"EOF"
71+
COPY --from=builder \
72+
--chown=$USER_NAME:$USER_NAME "$VENV" "$VENV"
11173

112-
# shared history
113-
HISTFILE=/var/tmp/.bash_history
114-
HISTFILESIZE=100
115-
HISTSIZE=100
74+
COPY --chown=$USER_NAME:$USER_NAME ./app/ ${WORKDIR}/
11675

117-
stty -ixon
118-
119-
# fzf
120-
[ -f ~/.fzf.bash ] && . ~/.fzf.bash
121-
122-
# asdf
123-
# https://asdf-vm.com/guide/getting-started.html
124-
export ASDF_DIR="$HOME/.asdf"
125-
[[ -f "${ASDF_DIR}/asdf.sh" ]] && . "${ASDF_DIR}/asdf.sh"
126-
127-
# homebrew
128-
export BREW_PREFIX="/home/linuxbrew/.linuxbrew/bin"
129-
[[ -f "${BREW_PREFIX}/brew" ]] && eval "$(${BREW_PREFIX}/brew shellenv)"
130-
131-
# aliases
132-
alias ..='cd ../'
133-
alias ...='cd ../../'
134-
alias ll='ls -la --color=auto'
135-
136-
EOF
137-
138-
FROM dev AS runner
139-
140-
# change working directory
141-
WORKDIR /app
76+
# standardise on locale, don't generate .pyc, enable tracebacks on seg faults
77+
ENV LANG C.UTF-8
78+
ENV LC_ALL C.UTF-8
79+
ENV PYTHONDONTWRITEBYTECODE 1
80+
ENV PYTHONFAULTHANDLER 1
14281

143-
# $PATH
144-
ENV PATH=$VENV_PATH/bin:$HOME/.local/bin:$PATH
82+
USER $USER_NAME
14583

146-
# port needed by app
14784
EXPOSE 8000
14885

149-
# run container indefinitely
15086
CMD ["sleep", "infinity"]
151-
152-
# metadata
153-
LABEL org.opencontainers.image.title="python-class"
87+
# CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
88+
# CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Variables with defaults that can be overridden
2+
variable "REGISTRY" {
3+
default = "ghcr.io"
4+
}
5+
6+
variable "IMAGE_NAME" {
7+
default = "mvp"
8+
}
9+
10+
variable "TAG" {
11+
default = "latest"
12+
}
13+
14+
// Base target with shared configuration
15+
target "docker-metadata-action" {
16+
tags = [
17+
"${REGISTRY}/${IMAGE_NAME}:${TAG}",
18+
"${REGISTRY}/${IMAGE_NAME}:latest",
19+
]
20+
}
21+
22+
// Default target that extends the base
23+
target "build" {
24+
inherits = ["docker-metadata-action"]
25+
context = "."
26+
dockerfile = "Dockerfile"
27+
// Platform will be set from GitHub Actions
28+
// cache-from and cache-to will also be set from GitHub Actions
29+
args = {
30+
PYTHON_VERSION = "3.12.10"
31+
// Additional build args can be defined here
32+
}
33+
// Output image will be pushed if push=true is set in GitHub Actions
34+
}
35+
36+
// Group target to build both platforms
37+
group "default" {
38+
targets = ["build"]
39+
}
40+
41+
// Optional specific targets for each platform if needed
42+
target "amd64" {
43+
inherits = ["build"]
44+
platforms = ["linux/amd64"]
45+
cache-from = ["type=gha,scope=linux/amd64"]
46+
cache-to = ["type=gha,mode=max,scope=linux/amd64"]
47+
}
48+
49+
target "arm64" {
50+
inherits = ["build"]
51+
platforms = ["linux/arm64"]
52+
cache-from = ["type=gha,scope=linux/arm64"]
53+
cache-to = ["type=gha,mode=max,scope=linux/arm64"]
54+
// Optional arm64-specific args
55+
args = {
56+
PYTHON_VERSION = "3.12.10"
57+
OPENBLAS_NUM_THREADS = "1"
58+
MKL_NUM_THREADS = "1"
59+
NUMEXPR_NUM_THREADS = "1"
60+
}
61+
}
62+
63+
// Matrix build target if you prefer to use it directly in bake
64+
target "multi-platform" {
65+
inherits = ["build"]
66+
platforms = ["linux/amd64", "linux/arm64"]
67+
}

{{cookiecutter.project_slug}}/taskfiles/docker.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,73 @@ tasks:
115115
cmds:
116116
- docker system prune --all --force
117117
- docker builder prune --all --force
118+
119+
create-builder:
120+
desc: "Create a local docker buildx builder"
121+
cmds:
122+
- |
123+
docker buildx create \
124+
--name multi-platform \
125+
--node multi-platform \
126+
--platform linux/arm64/v8,linux/amd64 \
127+
--driver=docker-container \
128+
--use \
129+
--bootstrap
130+
status:
131+
- docker buildx inspect multi-platform > /dev/null 2>&1
132+
133+
validate:
134+
desc: Validate the docker-bake.hcl file
135+
vars:
136+
BAKE_OUTPUT:
137+
sh: docker buildx bake --file docker-bake.hcl --print 2>&1 || true
138+
VALIDATION_ERROR:
139+
sh: echo "{{.BAKE_OUTPUT}}" | grep -q "ERROR:" && echo "true" || echo "false"
140+
preconditions:
141+
- sh: "test {{.VALIDATION_ERROR}} = false"
142+
msg: |
143+
Docker bake file is invalid. Error details:
144+
{{.BAKE_OUTPUT}}
145+
cmds:
146+
- cmd: echo "Docker bake file is valid"
147+
silent: true
148+
149+
buildx:
150+
desc: "Build using docker buildx bake"
151+
summary: |
152+
Build using docker buildx bake with the specified target.
153+
154+
AVAILABLE TARGETS
155+
- build (default): Builds the main image
156+
- amd64: Builds specifically for AMD64 platform
157+
- arm64: Builds specifically for ARM64 platform
158+
- multi-platform: Builds for both AMD64 and ARM64 platforms
159+
160+
USAGE
161+
task docker:buildx
162+
task docker:buildx -- amd64
163+
task docker:buildx -- arm64
164+
task docker:buildx -- multi-platform
165+
deps:
166+
- validate
167+
- create-builder
168+
cmds:
169+
- |
170+
if [ -z "{{.CLI_ARGS}}" ]; then
171+
TARGET="build"
172+
else
173+
case "{{.CLI_ARGS}}" in
174+
build|amd64|arm64|multi-platform)
175+
TARGET="{{.CLI_ARGS}}"
176+
;;
177+
*)
178+
echo "Error: Invalid target '{{.CLI_ARGS}}'"
179+
echo "Valid targets are: build, amd64, arm64, multi-platform"
180+
exit 1
181+
;;
182+
esac
183+
fi
184+
185+
docker buildx bake \
186+
--file docker-bake.hcl \
187+
$TARGET

0 commit comments

Comments
 (0)