Spaces:
Sleeping
Sleeping
File size: 7,412 Bytes
1724d6c 48447bb 1724d6c c250fa5 | 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 | # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# β disaster_grid β Hugging Face Spaces Dockerfile β
# β Target : Hugging Face Spaces "Blank Docker" template β
# β Runtime : Python 3.10-slim Β· FastAPI Β· Uvicorn Β· openenv-core β
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ββ Stage 1: Base image ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# python:3.10-slim is chosen over the full image (~900 MB) because our RL
# environment has no C-extension build requirements that need a compiler at
# runtime. This keeps the final layer under ~400 MB, which matters for cold-
# start latency on Hugging Face Spaces free-tier hardware.
FROM python:3.10-slim
# ββ Stage 2: System dependencies ββββββββββββββββββββββββββββββββββββββββββββββ
# We install only what is strictly required:
# curl β health-check probes from the Spaces infra ping /health
# build-essential β needed if any transitive dep compiles a C extension
# The apt cache is purged in the same RUN layer to avoid bloating the image
# with package index files (~30 MB on slim images).
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# ββ Stage 3: Non-root user βββββββββββββββββββββββββββββββββββββββββββββββββββββ
# CRITICAL (Hugging Face requirement):
# Hugging Face Spaces executes containers as a non-root user with uid=1000.
# If the image does not pre-create this user, the runtime uid has no entry in
# /etc/passwd, which breaks libraries that call getpwuid() (e.g. huggingface_hub,
# transformers' caching layer).
#
# We create:
# - group "user" gid=1000
# - user "user" uid=1000, home=/home/user, no password, no sudo
#
# --no-log-init suppresses large sparse-file writes on some kernels.
RUN groupadd --gid 1000 user \
&& useradd --uid 1000 --gid 1000 --no-log-init --create-home user
# ββ Stage 4: Working directory + ownership βββββββββββββββββββββββββββββββββββββ
# CRITICAL (Hugging Face requirement):
# The /app directory must be owned by uid=1000 BEFORE we switch users.
# If we create /app as root and then switch to user, any write at runtime
# (e.g. openenv writing episode logs, Pydantic schema cache) will raise
# PermissionError. chown here costs nothing at build time and prevents an
# entire class of runtime failures.
WORKDIR /app
RUN chown -R user:user /app
# ββ Stage 5: PATH β expose local pip binaries ββββββββββββββββββββββββββββββββββ
# CRITICAL (Hugging Face requirement):
# When pip installs packages as a non-root user it places console-script
# entry points (e.g. `uvicorn`, `openenv`) in ~/.local/bin, NOT in /usr/bin.
# Without this PATH addition the CMD instruction cannot find `uvicorn` and
# the container exits immediately with "exec: uvicorn: not found".
ENV PATH="/home/user/.local/bin:${PATH}"
# ββ Stage 6: Switch to non-root user ββββββββββββββββββββββββββββββββββββββββββ
# All subsequent RUN, COPY, and CMD instructions execute as uid=1000.
# Placing USER before COPY means the copied files are owned by user, not root,
# matching what the Spaces runtime expects.
USER user
# ββ Stage 7: Install Python dependencies ββββββββββββββββββββββββββββββββββββββ
# We copy pyproject.toml first (before the rest of the source) to exploit
# Docker's layer cache: as long as pyproject.toml is unchanged, Docker will
# reuse the pip-install layer even when app.py or src/ is edited. This
# reduces incremental build time from ~3 min to ~10 s on typical CI runners.
#
# --no-cache-dir : prevents pip from writing the HTTP cache to disk
# (~200 MB saved in the image layer)
# --user : installs into ~/.local (required since we are not root)
COPY --chown=user:user . /app
RUN pip install --no-cache-dir --user .
# ββ Stage 8: Copy project source ββββββββββββββββββββββββββββββββββββββββββββββ
# Copied after the dependency install layer so that editing src/ or app.py
# does not invalidate the expensive pip install cache step above.
COPY --chown=user:user . /app
# ββ Stage 9: Expose port βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# CRITICAL (Hugging Face requirement):
# Hugging Face Spaces routes all external HTTPS traffic to internal port 7860.
# Using any other port (e.g. 8000, 8080) means the Space will start but will
# return a 502 Bad Gateway for every request, with no error message in the
# build logs β one of the most confusing HF deployment failure modes.
EXPOSE 7860
# ββ Stage 10: Health check βββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Hugging Face Spaces monitors container liveness via HTTP. This HEALTHCHECK
# tells Docker (and the Spaces infra) that the app is ready once the /health
# endpoint responds with 2xx. The 30 s start-period gives Uvicorn time to
# load the openenv environment before the first probe fires.
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl --fail --silent http://localhost:7860/health || exit 1
# ββ Stage 11: Default command ββββββββββββββββββββββββββββββββββββββββββββββββββ
# Uvicorn is launched in exec form (JSON array) rather than shell form so that
# SIGTERM from the Spaces orchestrator is delivered directly to the Uvicorn
# process, enabling graceful shutdown instead of a SIGKILL after the timeout.
#
# --host 0.0.0.0 : CRITICAL β binds to all interfaces so the Spaces proxy
# can reach the container. 127.0.0.1 or localhost would
# make the app invisible to the outside world.
# --port 7860 : must match the EXPOSE directive and the Spaces requirement.
# --workers 1 : single worker is safer for stateful RL environments;
# increase to 2+ only after verifying episode state is not
# shared across worker processes.
CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"] |