# ============================================================================ # 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"]