File size: 4,309 Bytes
b163d5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e11cac9
b163d5f
 
 
de62154
 
 
 
 
 
 
 
b163d5f
 
 
 
 
 
 
 
 
 
 
 
8573ebf
b163d5f
a24d3c8
b163d5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e11cac9
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
# =============================================================================
# Planetary Rover Navigation Simulator β€” Dockerfile
# =============================================================================
# Target platform  : Hugging Face Spaces (Docker SDK)
# Exposed port     : 7860  (HF Spaces default)
# Python version   : 3.12-slim (smallest image with a stable C runtime)
#
# Build strategy β€” two stages:
#   1. builder  β€” installs pip packages into an isolated prefix so the
#                 final image contains no build tools (gcc, pip, wheel, etc.)
#   2. runtime  β€” copies only the installed packages + app source;
#                 result is ~60 % smaller than a single-stage build.
#
# Layer ordering is optimised for cache reuse:
#   Layer 1  system packages        (changes rarely)
#   Layer 2  requirements.txt COPY  (changes only when deps change)
#   Layer 3  pip install            (invalidated only when layer 2 changes)
#   Layer 4  application source     (invalidated on every code change)
#
# This means rebuilding after a code-only edit reuses the expensive pip
# install layer and completes in seconds instead of minutes.
# =============================================================================

# ── Stage 1: dependency builder ───────────────────────────────────────────────
FROM python:3.12-slim AS builder

# Prevent .pyc files and enable unbuffered stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /build

# Install only the build tools we actually need, then clean the apt cache
# in the same RUN layer so it never lands in the final image.
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        gcc \
        libffi-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy just the requirements file first.
# Docker cache: this layer is only invalidated when requirements.txt changes.
COPY requirements.txt .

# Install into /install prefix (not site-packages) so the runtime stage
# can COPY the entire prefix without carrying pip, setuptools, or wheel.
RUN pip install --no-cache-dir --upgrade pip \
    && pip install --no-cache-dir \
        --prefix=/install \
        --no-warn-script-location \
        -r requirements.txt


# ── Stage 2: minimal runtime ──────────────────────────────────────────────────
FROM python:3.12-slim AS runtime

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PATH=/install/bin:$PATH \
    # Add the /install prefix to the module search path
    PYTHONPATH=/install/lib/python3.12/site-packages

# Triton kernels used by Unsloth compile small helpers at runtime and
# require a C/C++ compiler to be available in the runtime image.
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        gcc \
        g++ \
    && rm -rf /var/lib/apt/lists/*

# Non-root user β€” Hugging Face Spaces requires this for security.
# UID 1000 matches the default HF Spaces user.
RUN useradd --uid 1000 --create-home --shell /bin/bash rover

WORKDIR /app

# Copy installed packages from builder (no pip, no compiler, no build cache)
COPY --from=builder /install /install

# Copy application source last β€” maximises cache reuse for code iterations.
# Only the files the server actually needs at runtime:
COPY main.py        ./main.py
COPY index.html     ./index.html
COPY inference.py    ./inference.py
COPY train.py        ./train.py
COPY openenv.yaml   ./openenv.yaml

# Transfer ownership to the non-root user
RUN chown -R rover:rover /app

USER rover

# Hugging Face Spaces routes external traffic to port 7860.
# Uvicorn is started with:
#   --workers 1      single worker β€” episode state lives in-process memory
#   --loop uvloop    fastest async event loop (installed via uvicorn[standard])
#   --access-log     request log visible in HF Spaces logs panel
EXPOSE 7860

HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/tasks')"

CMD ["sh", "-c", "python -m uvicorn main:app --host 0.0.0.0 --port 7860 & sleep 10 && python train.py"]