# syntax=docker/dockerfile:1 # # Hardened, browsable Tor Browser for Hugging Face Spaces (Docker SDK). # Architecture: # tor (system daemon, multi-bridge PTs) -> Firefox/Tor-Browser profile (JS off, locked prefs) # -> Xvfb -> x11vnc -> websockify/noVNC -> nginx (basic auth, WS proxy) -> HF public URL (port 7860) # # IMPORTANT: HF Spaces runs Docker containers as UID 1000 at runtime regardless # of root-looking build steps (see https://huggingface.co/docs/hub/spaces-sdks-docker # "The container runs with user ID 1000"). There is no root, no sudo, no gosu at # runtime here on purpose — every directory the app needs to write to is created # and chowned to `user` (uid 1000) at BUILD time (when we genuinely are root), # and every process just runs directly as `user`. FROM debian:bookworm-slim ARG TORBROWSER_VERSION=15.0.17 ARG TZ=Etc/UTC ENV DEBIAN_FRONTEND=noninteractive \ TZ=${TZ} \ HOME=/home/user \ DISPLAY=:99 \ SCREEN_GEOMETRY=1280x800x24 # ---- base packages ----------------------------------------------------- # (gosu dropped: nothing switches users at runtime anymore) RUN apt-get update && apt-get install -y --no-install-recommends \ tor obfs4proxy snowflake-client \ xvfb x11vnc novnc websockify \ nginx-light apache2-utils \ curl wget gnupg ca-certificates bzip2 xz-utils netcat-openbsd xxd \ fonts-liberation libgtk-3-0 libdbus-glib-1-2 libasound2 dbus-x11 \ libx11-xcb1 libxtst6 libnss3 libxss1 x11-utils supervisor \ && rm -rf /var/lib/apt/lists/* # ---- non-root user (uid 1000, matching what HF Spaces runs the container as) -- RUN useradd -m -u 1000 -d /home/user -s /bin/bash user # ---- fetch + verify Tor Browser (Browser/ component only; we run our own tor) ---- # NOTE: at build time, check https://www.torproject.org/download/ for the current # version and update TORBROWSER_VERSION. This step verifies the GPG signature # against the Tor Browser Developers signing key before extracting anything. WORKDIR /opt RUN set -eux; \ ARCH_URL="https://www.torproject.org/dist/torbrowser/${TORBROWSER_VERSION}/tor-browser-linux-x86_64-${TORBROWSER_VERSION}.tar.xz"; \ wget -q --timeout=30 --tries=3 "${ARCH_URL}" -O tb.tar.xz; \ wget -q --timeout=30 --tries=3 "${ARCH_URL}.asc" -O tb.tar.xz.asc; \ curl -sS --connect-timeout 10 --max-time 30 --retry 3 \ https://openpgpkey.torproject.org/.well-known/openpgpkey/torproject.org/hu/kounek7zrdx745qydx6p59t9mqjpuhdf \ | gpg --import - || true; \ gpg --list-keys 4E2C6E8793298290 || \ gpg --keyserver hkps://keys.openpgp.org --recv-keys \ --keyserver-options timeout=20 EF6E286DDA85EA2A4BA7DE684E2C6E8793298290; \ gpg --batch --verify tb.tar.xz.asc tb.tar.xz; \ tar -xJf tb.tar.xz; \ rm -f tb.tar.xz tb.tar.xz.asc; \ chown -R user:user /opt/tor-browser # ---- config files -------------------------------------------------------- COPY torrc /etc/tor/torrc COPY nginx.conf /etc/nginx/nginx.conf COPY firefox-prefs/autoconfig.js /opt/tor-browser/Browser/defaults/pref/autoconfig.js COPY firefox-prefs/tor-browser.cfg /opt/tor-browser/Browser/tor-browser.cfg COPY entrypoint.sh /entrypoint.sh COPY bootstrap-test.sh /usr/local/bin/bootstrap-test.sh COPY healthcheck.sh /usr/local/bin/healthcheck.sh COPY idle-watcher.sh /usr/local/bin/idle-watcher.sh # ---- fix every permission the app will ever need, while we still have root ---- RUN chmod +x /entrypoint.sh /usr/local/bin/bootstrap-test.sh /usr/local/bin/healthcheck.sh /usr/local/bin/idle-watcher.sh && \ mkdir -p /var/lib/tor /var/log/tor && \ chown -R user:user /var/lib/tor /var/log/tor /etc/tor && \ chmod 700 /var/lib/tor && \ mkdir -p /var/www /var/www/wait /var/www/error && \ chown -R user:user /var/www /var/log/nginx /var/lib/nginx /etc/nginx && \ touch /etc/nginx/.htpasswd && \ chown user:user /etc/nginx/.htpasswd && \ mkdir -p /tmp/tb-downloads /dev/shm && \ chown user:user /tmp/tb-downloads # Firefox profile lives in tmpfs at runtime (see entrypoint.sh) -> nothing persists to disk. # /dev/shm and /tmp are already world-writable (sticky 1777), no chown needed there. USER user WORKDIR /home/user EXPOSE 7860 HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \ CMD /usr/local/bin/healthcheck.sh ENTRYPOINT ["/entrypoint.sh"]