Skip to content

Commit 789fd4b

Browse files
Merge pull request #236 from lcox74/docker-hardening
Harden the production Docker image
2 parents f0d3f03 + e9d68f8 commit 789fd4b

4 files changed

Lines changed: 198 additions & 147 deletions

File tree

.dockerignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Version control
2+
.git
3+
.gitignore
4+
5+
# Github Workflows
6+
.github
7+
8+
# Secrets and environment
9+
.env
10+
.env.example
11+
12+
# Python caches and artifacts
13+
__pycache__/
14+
.venv
15+
16+
# Test and tooling caches
17+
.pytest_cache/
18+
19+
# Local dev tooling
20+
dev/
21+
tests/
22+
23+
# Docker
24+
Dockerfile
25+
.dockerignore
26+
docker-compose.yml
27+
28+
# Docs and meta
29+
README.md
30+
LICENSE
31+
32+
# OS
33+
**/.DS_STORE

Dockerfile

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
FROM python:3.13-slim AS python-base
1+
# ---- python-base
2+
# Common Python runtime and environment settings shared by all stages.
3+
FROM python:3.13-alpine AS python-base
24

3-
# Environment variables that should exist in all images.
45
ENV PYTHONUNBUFFERED=1 \
56
PIP_DISABLE_PIP_VERSION_CHECK=1 \
67
PIP_DEFAULT_TIMEOUT=100 \
@@ -9,40 +10,59 @@ ENV PYTHONUNBUFFERED=1 \
910
POETRY_CACHE_DIR='/var/cache/pypoetry'
1011

1112

12-
# poetry-base stage installs Poetry and installs prod deps
13+
# ---- poetry-base
14+
# Installs Poetry and production dependencies.
1315
FROM python-base AS poetry-base
1416

1517
WORKDIR /app
18+
1619
RUN pip install "poetry==$POETRY_VERSION" && poetry --version
1720

21+
# Install production dependencies separately so source changes do not
22+
# invalidate the dependency cache.
1823
COPY pyproject.toml poetry.lock ./
19-
RUN poetry install --without=dev
24+
RUN poetry install --without=dev --no-root
2025

2126

22-
# dev stage continues off poetry-base to install dev deps
23-
# and have poetry available within the container.
27+
# ---- dev
28+
# Development image with Poetry and development dependencies available for
29+
# local development and testing.
2430
FROM poetry-base AS dev
2531

2632
ENV VIRTUAL_ENV=/app/.venv \
2733
PATH="/app/.venv/bin:$PATH"
2834

2935
WORKDIR /app
30-
RUN poetry install --with=dev
3136

37+
RUN poetry install --with=dev --no-root
38+
39+
EXPOSE 8080
3240
ENTRYPOINT ["python", "-m", "uqcsbot"]
3341

3442

35-
# prod stage creates the final image for production and excludes
36-
# poetry as it is unneeded on prod.
43+
# ---- prod
44+
# Final production image. Excludes Poetry, build tooling, package manager,
45+
# system shell, and runs as a non-root user. It only contains the application
46+
# and virtual environment.
3747
FROM python-base AS prod
3848

39-
ENV VIRTUAL_ENV=/app/.venv \
40-
PATH="/app/.venv/bin:$PATH"
49+
# Create an unprivileged runtime user.
50+
RUN addgroup -S -g 65532 nonroot \
51+
&& adduser -S -G nonroot -u 65532 -h /home/nonroot nonroot
4152

42-
COPY --from=poetry-base /app /app
53+
COPY --from=poetry-base --chown=nonroot:nonroot /app /app
54+
COPY --chown=nonroot:nonroot ./uqcsbot /app/uqcsbot
4355

4456
WORKDIR /app
45-
COPY ./uqcsbot ./uqcsbot
4657

47-
ENTRYPOINT ["python", "-m", "uqcsbot"]
58+
# Strip installer tooling from venv, and remove the package manager.
59+
RUN /app/.venv/bin/pip uninstall -y pip setuptools wheel 2>/dev/null || true ; \
60+
rm -rf /sbin/apk /etc/apk /usr/share/apk /var/cache/apk
4861

62+
USER nonroot
63+
64+
ENV VIRTUAL_ENV=/app/.venv \
65+
PATH="/app/.venv/bin:$PATH"
66+
67+
EXPOSE 8080
68+
ENTRYPOINT ["python", "-m", "uqcsbot"]

0 commit comments

Comments
 (0)