agent-first-http 0.7.2

Give your AI agent its own private browser — so it reads the real page, past logins and bot walls, without ever touching yours.
Documentation
# Runtime image for the afhttp *host* (the browser side).
#
# The driver (`afhttp fetch`/`cdp`/`upload`/…) is a thin client and runs directly
# wherever the agent is — it does NOT need this image. The host runs a real
# browser with Chromium's own sandbox disabled (the image sets AFHTTP_NO_SANDBOX,
# so the container itself is the isolation boundary; afhttp keeps the sandbox ON
# when run natively), holds browser profile state, and exposes a CDP endpoint;
# that is what belongs in a container. See docs/deployment.md.
#
# The afhttp binary comes from ONE of two source stages, picked by the
# AFHTTP_BIN_FROM build-arg:
#   builder    — compile from a source checkout (default; dev / `--from-source`).
#                Build context is the spore root:
#                  docker build -t afhttp-host -f container/docker/Dockerfile .
#   downloader — download the matching prebuilt release; what `afhttp container
#                install` uses. Needs --build-arg AFHTTP_VERSION + AFHTTP_TARGET.
# BuildKit only builds the selected stage, so the download path pulls no Rust
# toolchain and needs no source tree, while the from-source path makes no network
# call for a release. Optional backends are opt-in (chromium is always present):
#   docker build --build-arg WITH_BRAVE=1 --build-arg WITH_KASMVNC=1 ...
#
# This is NOT the test image — tests/Dockerfile.test (rust toolchain + all backends
# + coverage tooling, source mounted as a volume) is for build/test/clippy/coverage.

# Selects which source stage below provides the binary (see FROM ${AFHTTP_BIN_FROM}).
# Global (before the first FROM) so it is usable on a FROM line.
ARG AFHTTP_BIN_FROM=builder

# ── Source stage A: compile from a checkout (default) ──
FROM rust:bookworm AS builder
WORKDIR /src
# Only the crate inputs — NOT .cargo/ (its dev path override to
# ../agent-first-data/rust is absent here, so agent-first-data resolves from
# crates.io per Cargo.lock).
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
COPY assets/ assets/
COPY tests/ tests/
COPY examples/ examples/
# Embedded at compile time via include_str!: the skill (skill.rs) and the
# container build recipe + shared scripts (container.rs).
COPY skills/ skills/
COPY container/ container/
RUN cargo build --release --bin afhttp \
    && cp target/release/afhttp /usr/local/bin/afhttp

# ── Source stage B: download the matching prebuilt release ──
FROM debian:bookworm-slim AS downloader
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates curl \
    && rm -rf /var/lib/apt/lists/*
# Hard-pinned by the driver: its own version + the linux triple for the image
# arch. A 404 here means no release asset exists for this version/arch — the
# driver surfaces that and points at the from-source path.
ARG AFHTTP_VERSION
ARG AFHTTP_TARGET
RUN set -eu; \
    base="https://github.com/agentfirstkit/agent-first-http/releases/download/v${AFHTTP_VERSION}"; \
    archive="afhttp-v${AFHTTP_VERSION}-${AFHTTP_TARGET}.tar.gz"; \
    cd /tmp; \
    curl -fsSL -o "$archive" "${base}/${archive}"; \
    curl -fsSL -o "${archive}.sha256" "${base}/${archive}.sha256"; \
    sha256sum -c "${archive}.sha256"; \
    tar -xzf "$archive" -C /usr/local/bin afhttp; \
    chmod +x /usr/local/bin/afhttp

# ── Pick the source stage that provides /usr/local/bin/afhttp ──
FROM ${AFHTTP_BIN_FROM} AS afhttp_bin

# ── Runtime image (single source of truth for the host) ──
FROM debian:bookworm-slim

ENV DEBIAN_FRONTEND=noninteractive

# chromium (the always-on backend) + the libraries it needs to start, plus the
# small toolset install-backends.sh uses for the optional backends, plus tini to
# reap the browser's child processes (afhttp host is PID 1).
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        ca-certificates \
        chromium \
        curl \
        fonts-liberation \
        fonts-noto-cjk \
        libatk-bridge2.0-0 \
        libatk1.0-0 \
        libdrm2 \
        libgbm1 \
        libnss3 \
        libxcomposite1 \
        libxdamage1 \
        libxfixes3 \
        libxkbcommon0 \
        libxrandr2 \
        procps \
        tini \
        unzip \
        xdg-utils \
        xz-utils \
    && rm -rf /var/lib/apt/lists/*

# Optional backends — each opt-in, arch-guarded inside the shared installer that
# tests/Dockerfile.test also uses (single source of truth for pinned versions).
COPY container/docker/install-backends.sh /usr/local/bin/install-backends.sh
RUN chmod +x /usr/local/bin/install-backends.sh

ARG WITH_CHROME_HEADLESS_SHELL=0
ARG WITH_LIGHTPANDA=0
ARG WITH_FINGERPRINT_CHROMIUM=0
ARG WITH_CAMOUFOX=0
ARG WITH_BRAVE=0
ARG WITH_KASMVNC=0

RUN if [ "$WITH_CHROME_HEADLESS_SHELL" = "1" ]; then install-backends.sh chrome-headless-shell; fi
RUN if [ "$WITH_LIGHTPANDA" = "1" ]; then install-backends.sh lightpanda; fi
RUN if [ "$WITH_FINGERPRINT_CHROMIUM" = "1" ]; then install-backends.sh fingerprint-chromium; fi
RUN if [ "$WITH_CAMOUFOX" = "1" ]; then install-backends.sh camoufox; fi
RUN if [ "$WITH_BRAVE" = "1" ]; then install-backends.sh brave; fi
RUN if [ "$WITH_KASMVNC" = "1" ]; then install-backends.sh kasmvnc; fi

COPY --from=afhttp_bin /usr/local/bin/afhttp /usr/local/bin/afhttp
COPY container/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Non-root: AFHTTP_NO_SANDBOX makes chromium run with its sandbox off (the
# container is the boundary), so it does not need root. Profiles live under
# $XDG_DATA_HOME (a volume). KasmVNC env is harmless when Xvnc isn't built in —
# the host only reads it for `--takeover-provider kasmvnc`.
RUN useradd --create-home --uid 10001 afhttp \
    && mkdir -p /data \
    && chown -R afhttp:afhttp /data
ENV XDG_DATA_HOME=/data \
    AFHTTP_NO_SANDBOX=1 \
    AFHTTP_KASMVNC_BIN=/usr/bin/Xvnc \
    AFHTTP_KASMVNC_WEB_ROOT=/usr/share/kasmvnc/www

USER afhttp
VOLUME ["/data"]
EXPOSE 9222

ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"]