-
Notifications
You must be signed in to change notification settings - Fork 201
Expand file tree
/
Copy pathDockerfile.alpine
More file actions
140 lines (122 loc) · 5.37 KB
/
Dockerfile.alpine
File metadata and controls
140 lines (122 loc) · 5.37 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
# ============================================================================
# Open Terminal — Alpine Image
# ============================================================================
#
# The smallest possible Open Terminal image. Built on Alpine Linux with
# musl libc, this image is ~100 MB — great for edge deployments, CI
# runners, and environments where every megabyte counts.
#
# Build:
# docker build -f Dockerfile.alpine -t open-terminal:alpine .
#
# Run:
# docker run -d -p 8000:8000 -e OPEN_TERMINAL_API_KEY=secret open-terminal:alpine
#
# Customize:
# - Want extra apk packages? Add them to the "Runtime packages" section below.
# - Want extra pip packages? Add them to the "pip install" line in the builder.
# - Need glibc or heavier tools? Use the slim (Debian) or full image instead.
#
# ⚠️ Alpine uses musl libc. Most pure-Python packages work fine, but
# some C-extension wheels (numpy, pandas, etc.) may need to be compiled
# from source, which is slower. If you hit issues, try the slim image.
#
# ============================================================================
# --------------------------------------------------------------------------
# Stage 1: Builder
# Compiles Python dependencies in an isolated prefix. Alpine needs
# musl-dev and gcc for most C extensions.
# --------------------------------------------------------------------------
FROM python:3.12.13-alpine AS builder
WORKDIR /src
# Build-time system dependencies.
# ➡️ If a pip package needs extra C headers, add the -dev package here
# (e.g. "postgresql-dev" for psycopg2, "libxml2-dev" for lxml).
RUN apk add --no-cache \
build-base \
gcc \
musl-dev \
libffi-dev
COPY pyproject.toml README.md uv.lock* ./
COPY open_terminal/ open_terminal/
# Install the app + dependencies into /install so we can cherry-pick them.
# ➡️ To add extra pip packages to the alpine image, append them here:
# e.g. pip install --no-cache-dir --prefix=/install . httpx polars
RUN pip install --no-cache-dir --prefix=/install .
# --------------------------------------------------------------------------
# Stage 2: Runtime
# Tiny Alpine runtime. No compilers, no build tools, no sudo.
# --------------------------------------------------------------------------
FROM python:3.12.13-alpine
# ── Runtime system packages ────────────────────────────────────────────────
#
# These are the only apk packages in the final image. Kept intentionally
# tiny. If you need something extra, just add it below.
#
# Core: tini (PID 1), su-exec (drop privileges, Alpine's gosu)
# Utilities: curl, git, jq, less, procps (ps/top)
# Firewall: iptables, ipset, dnsmasq (egress whitelist)
# Shell: bash (the terminal API spawns bash sessions)
#
# ➡️ Want more tools? Add them here, one per line for easy diffs:
# e.g. vim \
# sqlite \
#
RUN apk add --no-cache \
tini \
su-exec \
curl \
git \
jq \
less \
procps \
bash \
iptables \
ipset \
dnsmasq \
libcap \
libcap-utils \
ca-certificates
# Copy the pre-built Python packages from the builder stage.
COPY --from=builder /install /usr/local
WORKDIR /app
COPY . .
# Alpine patches ship fast. Upgrade base packages to pick up security fixes
# that landed after the pinned base image was published.
RUN apk upgrade --no-cache
# Install the app itself (fast — all deps are already present).
#
# Cleanup removes ~20 MB of unnecessary files:
# - pip itself (not needed at runtime)
# - __pycache__ / .pyc / .pyo bytecode (regenerated on first import)
# - test directories inside packages
#
RUN pip install --no-cache-dir --no-deps . \
&& pip cache purge 2>/dev/null || true \
&& pip uninstall -y pip setuptools 2>/dev/null || true \
&& find /usr/local/lib/python3.12 -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true \
&& find /usr/local/lib/python3.12 -type f -name "*.pyc" -o -name "*.pyo" -delete 2>/dev/null || true \
&& find /usr/local/lib/python3.12 -type d -name "tests" -o -name "test" | xargs rm -rf 2>/dev/null || true
# ── Non-root user ─────────────────────────────────────────────────────────
#
# The app runs as "user" (UID 1000) with NO sudo access.
# The entrypoint handles any root-level setup (iptables, chown) via su-exec.
#
RUN adduser -D -s /bin/bash -u 1000 user
COPY entrypoint-slim.sh /app/entrypoint-slim.sh
RUN chmod +x /app/entrypoint-slim.sh
# ── NOTE: We do NOT set "USER user" here ──────────────────────────────────
#
# The container starts as root so the entrypoint can:
# 1. Fix /home/user ownership on bind mounts
# 2. Set up iptables egress rules (if configured)
# 3. Drop privileges via su-exec → runs the app as "user"
#
# If you don't need the egress firewall, you can add "USER user" here
# and simplify the entrypoint — but su-exec handles it cleanly either way.
#
ENV SHELL=/bin/bash
EXPOSE 8000
# tini is PID 1 — reaps zombies and forwards signals cleanly.
ENTRYPOINT ["/sbin/tini", "--", "/app/entrypoint-slim.sh"]
CMD ["run"]