-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile.api
More file actions
79 lines (65 loc) · 3.77 KB
/
Copy pathDockerfile.api
File metadata and controls
79 lines (65 loc) · 3.77 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
FROM python:3.11-slim@sha256:ae52c5bef62a6bdd42cd1e8dffef86b9cd284bde9427da79839de7a4b983e7ca AS builder
WORKDIR /build
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
COPY pyproject.toml /build/pyproject.toml
COPY README.md /build/README.md
COPY LICENSE /build/LICENSE
COPY CHANGELOG.md /build/CHANGELOG.md
COPY requirements.txt /build/requirements.txt
COPY src /build/src
RUN pip install --no-cache-dir --upgrade pip build hatchling \
&& python -m build --wheel --outdir /tmp/dist
FROM python:3.11-slim@sha256:ae52c5bef62a6bdd42cd1e8dffef86b9cd284bde9427da79839de7a4b983e7ca
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV AGENTFLOW_ENTITY_CONTRACTS_DIR=/app/contracts/entities
# Non-root HOME (see USER below): anything that defaults to ~/.cache has
# somewhere writable to fall back to instead of the container's root `/`.
ENV HOME=/app
# .dockerignore drops config/api_keys.yaml, config/webhooks.yaml and
# config/tenants.yaml from the build context (audit P1-4): this COPY cannot
# see them, so they cannot land in an image layer. Runtime gets the real
# files from a compose bind mount or a Helm Secret mount instead.
COPY requirements-docker.lock /tmp/requirements-docker.lock
COPY config /app/config
COPY contracts /app/contracts
COPY --from=builder /tmp/dist /tmp/dist
# Audit P1-3: every third-party distribution comes from the hash-pinned
# export of uv.lock (requirements-docker.lock, extras cloud+postgres) — two
# builds from the same inputs install the same dependency set, and a
# tampered wheel fails the hash check. The project wheel itself then
# installs with --no-deps (its dependencies are exactly what the lock just
# installed), and `pip check` proves the result is a consistent environment
# instead of asserting it in a comment. CI keeps the lock honest: the
# lock-check job re-exports uv.lock and diffs it against this file.
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir --upgrade setuptools==82.0.1 wheel==0.47.0 \
&& pip install --no-cache-dir --require-hashes -r /tmp/requirements-docker.lock \
&& wheel="$(find /tmp/dist -name '*.whl' -print -quit)" \
&& pip install --no-cache-dir --no-deps "${wheel}" \
&& pip check \
&& rm -rf /tmp/dist /tmp/requirements-docker.lock
# Non-root runtime (audit P1-4): this image ran as root by default. Helm
# already compensates (values.yaml containerSecurityContext.runAsUser:
# 10001); this gives the standalone image the same uid/gid so both paths
# agree. /app/data is pre-created and chowned so a fresh docker-compose named
# volume mounted there inherits agentflow ownership (Docker seeds a new named
# volume from the image directory it overlays, permissions included), and
# AGENTFLOW_USAGE_DB_PATH's default (agentflow_api.duckdb, relative to
# WORKDIR) can still be created directly under /app.
RUN groupadd --gid 10001 agentflow \
&& useradd --uid 10001 --gid agentflow --no-create-home --shell /usr/sbin/nologin agentflow \
&& mkdir -p /app/data \
&& chown -R agentflow:agentflow /app
USER agentflow:agentflow
EXPOSE 8000
# /health/ready, not /v1/health (audit P0-3). /v1/health always answers 200 and
# puts its verdict in the payload, so a healthcheck against it reports a
# container as healthy while the serving backend is unreachable and every read
# is failing. /health/ready answers 503 in that state and urlopen raises.
# docker-compose.prod.yml and the Helm probes already say this; the standalone
# image was the one place still asking the question that cannot get a no.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=3).read()" || exit 1
CMD ["uvicorn", "src.serving.api.main:app", "--host", "0.0.0.0", "--port", "8000"]