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
| # syntax=docker/dockerfile:1.7
#
##############################
# builder
##############################
FROM python:3.13-slim AS builder
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1 \
PIP_ONLY_BINARY=:all: \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# Pre-cache manifests
COPY pyproject.toml README.md LICENSE* ./
# Fix HIGH vulnerable issue: CVE-2025-62727 by upgrading starlette and fastapi.
RUN --mount=type=cache,target=/root/.cache/pip python - <<'PY'
import tomllib, pathlib, re
def parse_req(s:str):
m = re.match(r'^\s*([A-Za-z0-9_.-]+)(\[[^\]]+\])?\s*(.*)$', s)
if m:
name, extras, rest = m.group(1), (m.group(2) or ''), (m.group(3) or '')
return name, extras, rest
name = re.split(r'[><=~!; ]', s, 1)[0]
return name, '', s[len(name):]
data = tomllib.loads(pathlib.Path('pyproject.toml').read_text())
deps = data.get('project', {}).get('dependencies', [])
safe = []
present = set()
for d in deps:
name, extras, rest = parse_req(d)
norm = name.lower().replace('_','-')
if norm == 'fastapi':
safe.append(f'fastapi{extras}>=0.118,<0.121')
else:
safe.append(d)
present.add(norm)
if 'starlette' not in present:
safe.append('starlette>=0.49.1,<0.50')
pathlib.Path('/requirements.safe.txt').write_text('\n'.join(safe) + '\n')
print('Resolved safe deps:', *safe, sep='\n- ')
PY
# Wheel ALL dependencies from the safe list
RUN --mount=type=cache,target=/root/.cache/pip \
pip wheel --wheel-dir /wheels -r /requirements.safe.txt
# Build wheel of the project itself
COPY src/ ./src/
RUN --mount=type=cache,target=/root/.cache/pip \
pip wheel --wheel-dir /wheels .
##############################
# runtime
##############################
FROM python:3.13-slim AS runtime
ARG VERSION=0.1.0
ARG VCS_REF=sha
ARG BUILD_DATE
LABEL org.opencontainers.image.title="python-service-template" \
org.opencontainers.image.description="Dockerfile contest build" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.licenses="Apache-2.0"
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONOPTIMIZE=2 \
HOST=0.0.0.0 \
PORT=5000 \
WORKERS=1 \
APP_VERSION=$VERSION \
GIT_COMMIT_SHA=$VCS_REF
# Non-root
RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin appuser
WORKDIR /home/appuser
# Install offline: install ALL safe deps, then the app wheel with --no-deps
COPY --from=builder /wheels /wheels
COPY --from=builder /requirements.safe.txt /requirements.safe.txt
RUN pip install --no-index --find-links=/wheels -r /requirements.safe.txt \
&& pip install --no-index --find-links=/wheels --no-deps \
python-service-template --no-compile \
&& rm -rf /wheels /requirements.safe.txt
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=2s --start-period=10s --retries=3 \
CMD python -c "import sys, http.client; c=http.client.HTTPConnection('127.0.0.1', int(__import__('os').environ.get('PORT','5000')), timeout=1); c.request('GET','/health'); r=c.getresponse(); sys.exit(0 if r.status==200 else 1)" || exit 1
USER 10001:10001
CMD ["python", "-m", "python_service_template.app"]
|