-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
176 lines (151 loc) · 5.35 KB
/
Dockerfile
File metadata and controls
176 lines (151 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
FROM python:3.14-alpine AS base_image
LABEL \
maintainer="Arne Fahrenwalde <arne@fahrenwal.de>" \
description="DevContainer example for python projects" \
version="1.0"
# Linux specific environment variables
ENV \
BASE_DIR="/opt/app" \
LANG="C.UTF-8" \
LC_ALL="C.UTF-8" \
USER="worker" \
GROUP="worker"
# Python specific environment variables
ENV VIRTUAL_ENV="${BASE_DIR}/venv"
ENV \
PATH="${VIRTUAL_ENV}/bin:${PATH}" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH="${BASE_DIR}/src" \
PYTHONUNBUFFERED=1
# overridable variables
ARG \
TZ="Europe/Berlin" \
UID=1000 \
GID=1000
# update all packages and install procps as simple docker healthcheck
# trivy:ignore:DS-0025
RUN --mount=type=cache,id="apkcache",target="/etc/apk/cache" \
apk update \
&& apk upgrade \
&& apk add procps-ng
# set timezone
RUN ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime \
&& echo "${TZ}" > /etc/timezone
# remove pip from the image (uv will be installed in the build stage below)
RUN pip3 uninstall -y pip setuptools
# Create the base layout for the application
# /opt/app/
# mnt/ -> docker volumes (r/w)
# src/ -> project source code
# venv/ -> virtual environment
RUN mkdir -p \
"${BASE_DIR}/mnt" \
"${BASE_DIR}/src" \
"${VIRTUAL_ENV}"
# add lower privileged user and group, only assign the app directory to the user
RUN addgroup -g ${GID} ${GROUP} \
|| export GROUP=$(awk -F: -v gid=${GID} '$3 == gid { print $1 }' /etc/group) \
&& adduser -u ${UID} -G ${GROUP} -h "${BASE_DIR}" -H -D ${USER} \
&& chown -R ${UID}:${GID} "${BASE_DIR}" \
&& chmod -R 750 "${BASE_DIR}"
# use the unprivileged user by default
USER ${USER}
WORKDIR "${BASE_DIR}"
FROM base_image AS build_image
ENV \
PATH="${PATH}:${BASE_DIR}/.local/bin" \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT="${VIRTUAL_ENV}"
# install ruff, ty & uv
COPY --from=ghcr.io/astral-sh/ruff:latest --link /ruff /usr/local/bin/
COPY --from=ghcr.io/astral-sh/ty:latest --link /ty /usr/local/bin/
COPY --from=ghcr.io/astral-sh/uv:latest --link /uv /uvx /usr/local/bin/
# install trivy
COPY --from=ghcr.io/aquasecurity/trivy:latest --link /usr/local/bin/trivy /usr/local/bin/
# add requirements for dynamic versioning support
USER root
# trivy:ignore:DS-0025
RUN --mount=type=cache,id="apkcache",target="/etc/apk/cache" \
apk add git make
USER ${USER}
# add prek (pre-commit) and uv-dynamic-versioning & setup the app's virtual environment
RUN --mount=type=cache,id="usercache",uid=${UID},gid=${GID},target="${BASE_DIR}/.cache" \
uv tool install prek \
&& uv tool install uv-dynamic-versioning \
&& python3 -m venv --symlinks --without-pip "${VIRTUAL_ENV}"
# install app dependencies, continue building the DevContainer if it fails, ignore permission mismatches between host and container
RUN \
--mount=type=cache,id="usercache",uid=${UID},gid=${GID},target="${BASE_DIR}/.cache" \
--mount=type=bind,source="pyproject.toml",target="${BASE_DIR}/pyproject.toml" \
--mount=type=bind,source="README.md",target="${BASE_DIR}/README.md" \
--mount=type=bind,source="uv.lock",target="${BASE_DIR}/uv.lock" \
--mount=type=bind,source=".git",target="${BASE_DIR}/.git" \
--mount=type=bind,source="src",target="${BASE_DIR}/src" \
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0="safe.directory" GIT_CONFIG_VALUE_0="${BASE_DIR}" \
uv sync --no-default-groups || true
FROM build_image AS dev_image
# install system dependencies for development and debugging inside a DevContainer
USER root
# trivy:ignore:DS-0025
RUN --mount=type=cache,id="apkcache",target="/etc/apk/cache" \
apk add \
# required for VSCode
bash \
curl \
gnupg \
libstdc++ \
rsync \
# allow sudo inside the DevContainer
sudo \
# optional cli tools
bind-tools \
htop \
jq \
the_silver_searcher \
# misc
ca-certificates \
# nice to have zsh setup
fzf \
oh-my-zsh \
shadow \
zsh \
zsh-vcs
# switch default shell to zsh
RUN chsh -s $(which zsh) ${USER}
# allow sudo for local developer
RUN echo "${USER} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USER} \
&& chmod 0440 /etc/sudoers.d/${USER}
# ensure the "home" folder is writable
RUN chown ${UID}:${GID} "${BASE_DIR}"
USER ${USER}
# create mountpoints so that mounted volumes have the correct assigned permissions
RUN mkdir -p \
"${BASE_DIR}/.cache" \
"${BASE_DIR}/.vscode-server"
ENV \
RUFF_CACHE_DIR="${BASE_DIR}/.cache/ruff" \
TERM="xterm" \
UV_NO_DEV=0 \
WORKON_HOME="${VIRTUAL_ENV}"
CMD sleep infinity
FROM base_image AS release
# set file/folder ownership to root to prevent changes during runtime
COPY --chown=0:${GID} --from=build_image --link "${VIRTUAL_ENV}" "${VIRTUAL_ENV}"
# TODO: ensure your app is available and building wasn't skipped in build_image due to || true
RUN which app
USER root
# ensure mount points are user writable
RUN chown -R ${UID}:${GID} "${BASE_DIR}/mnt"
# remove package manager in release image
RUN apk --purge del apk-tools
# apply additional image hardening
RUN --mount=type=bind,source=".devcontainer/security/hardening.sh",target="/sbin/hardening.sh" \
hardening.sh
USER ${USER}
COPY --chown=0:${GID} --link ./src "${BASE_DIR}/src"
# TODO: change command if necessary
CMD [ "app" ]
# TODO: update the healthcheck according to your needs
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD pgrep -f "app" > /dev/null || exit 1