-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDockerfile
More file actions
109 lines (95 loc) · 4.89 KB
/
Copy pathDockerfile
File metadata and controls
109 lines (95 loc) · 4.89 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
# syntax=docker/dockerfile:1.7
#
# Container image for DigitalOcean App Platform (or any container host).
# Runs gunicorn directly — Whitenoise serves the collected static files
# from inside the same process, so there is no separate web server.
#
# Signing keys are NOT baked in. They must be mounted at runtime via
# SKEY_PATH / VKEY_PATH (e.g. DO App Platform secret files at /run/...,
# or a mounted volume). The ApiConfig.ready() check at startup will
# refuse to boot if they are missing.
FROM python:3.12-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1 \
# In a container the default file-based debug.log is wrong:
# gunicorn's stdout is what the platform captures, /app/collateral_provider/
# isn't writable by the unprivileged app user, and any file we did write
# would die with the container on restart. Send the rotating handler at
# /dev/null and emit JSON-per-line on stdout so log aggregators index
# fields cleanly. Operators can override either by setting the env var
# in their platform spec.
LOG_FILE=/dev/null \
LOG_FORMAT=json
# libsodium isn't strictly required (PyNaCl bundles its own), but build-essential
# would only be needed if a wheel were missing — every dep in requirements.txt
# ships manylinux wheels for cpython 3.12, so we can skip the toolchain.
# curl stays in the image for the health check / smoke test.
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# Run as a non-root user. App Platform doesn't strictly require it, but it's
# cheap defence-in-depth: a code-execution bug shouldn't have root inside the
# container.
RUN useradd --system --create-home --uid 10001 app
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
# Project layout: manage.py lives at /app/collateral_provider/manage.py.
# Everything else (known.hosts.json, etc.) lives at /app/.
COPY . /app/
# Static files for the landing page + DRF browsable API. Collectstatic
# needs settings.py to import successfully, which means PKH/SECRET_KEY/etc.
# must parse — pass throwaway values via env (apps.py skips key validation
# during collectstatic so the dummy SKEY/VKEY paths are fine).
RUN set -eux; \
export PKH=0000000000000000000000000000000000000000000000000000000000000000 \
DJANGO_SECRET_KEY=build-time-only-not-a-real-secret \
ENVIRONMENT=development \
PREPROD_TXID=0000000000000000000000000000000000000000000000000000000000000000 \
PREPROD_TXIDX=0 \
"PREPROD_NETWORK=--testnet-magic 1" \
MAINNET_TXID=0000000000000000000000000000000000000000000000000000000000000000 \
MAINNET_TXIDX=0 \
MAINNET_NETWORK=--mainnet; \
python collateral_provider/manage.py collectstatic --noinput
# Cache directory for the file-based throttle. Has to be writable at runtime.
# /app is owned by root after COPY; chown the slots that need writes.
# Pre-create /run/keys for the entrypoint's signing-key materialization
# (the app user can't mkdir under /run, which is root-owned).
RUN mkdir -p /app/collateral_provider/.cache /app/collateral_provider/staticfiles /run/keys \
&& chown -R app:app /app/collateral_provider/.cache /app/collateral_provider/staticfiles /run/keys \
&& chmod 700 /run/keys
USER app
WORKDIR /app/collateral_provider
EXPOSE 8080
# Entrypoint materializes SKEY_CONTENTS / VKEY_CONTENTS env vars to
# tmpfs files (/run/keys/) before exec'ing gunicorn. Operators using a
# mounted volume for keys can simply leave those env vars unset — the
# entrypoint skips the materialization and gunicorn picks up whatever
# SKEY_PATH / VKEY_PATH point to.
ENTRYPOINT ["/app/docker-entrypoint.sh"]
# Workers + threads. The whole product is a thin wrapper around a Koios
# HTTP call (~hundreds of ms p50, up to 5s read timeout). Sync workers
# would block one request per worker for the whole RTT — gthread lets
# each process juggle multiple in-flight requests while waiting on the
# network, so concurrent capacity ≈ workers * threads. 2 * 8 = 16 is
# plenty for a basic-xxs DO instance and uses negligible extra memory.
# The file-based cache backend shares throttle counters across workers
# on the same host. Bind to 8080 — the port DO App Platform expects.
#
# --keep-alive 75 holds the LB↔gunicorn TCP connection open between
# requests (gunicorn defaults to 2s, which forces a fresh accept() on
# every cold hit). --timeout 60 makes the worker timeout explicit so a
# hung Koios call doesn't get silently killed at the 30s default.
CMD ["gunicorn", "collateral_provider.wsgi:application", \
"--bind", "0.0.0.0:8080", \
"--worker-class", "gthread", \
"--workers", "2", \
"--threads", "8", \
"--timeout", "60", \
"--graceful-timeout", "30", \
"--keep-alive", "75", \
"--access-logfile", "-", \
"--error-logfile", "-"]