|
| 1 | +FROM debian:bookworm-slim |
| 2 | + |
| 3 | +RUN apt-get update && apt-get install -y --no-install-recommends \ |
| 4 | + ca-certificates \ |
| 5 | + curl \ |
| 6 | + git \ |
| 7 | + jq \ |
| 8 | + python3 \ |
| 9 | + ripgrep \ |
| 10 | + unzip \ |
| 11 | + && rm -rf /var/lib/apt/lists/* |
| 12 | + |
| 13 | +RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ |
| 14 | + && apt-get install -y --no-install-recommends nodejs \ |
| 15 | + && rm -rf /var/lib/apt/lists/* |
| 16 | + |
| 17 | +# pnpm via corepack, pinned and pre-activated into a world-readable/writable |
| 18 | +# cache so the container can run as an arbitrary host UID without downloading |
| 19 | +# pnpm at runtime. |
| 20 | +ENV COREPACK_HOME=/opt/corepack \ |
| 21 | + COREPACK_ENABLE_DOWNLOAD_PROMPT=0 |
| 22 | +RUN corepack enable \ |
| 23 | + && corepack prepare pnpm@10.28.1 --activate \ |
| 24 | + && chmod -R 0777 /opt/corepack |
| 25 | + |
| 26 | +# bun installed to a world-accessible location. The default installer targets |
| 27 | +# /root/.bun, which is unreadable (mode 0700) when the container runs as a |
| 28 | +# non-root host UID. |
| 29 | +ENV BUN_INSTALL=/opt/bun |
| 30 | +RUN curl -fsSL https://bun.sh/install | bash \ |
| 31 | + && ln -sf /opt/bun/bin/bun /usr/local/bin/bun \ |
| 32 | + && ln -sf /opt/bun/bin/bun /usr/local/bin/bunx \ |
| 33 | + && chmod -R a+rX /opt/bun |
| 34 | + |
| 35 | +# uv installed to a world-accessible location for the same reason as bun. |
| 36 | +ENV UV_INSTALL_DIR=/usr/local/bin |
| 37 | +RUN curl -fsSL https://astral.sh/uv/install.sh | bash \ |
| 38 | + && chmod a+rx /usr/local/bin/uv /usr/local/bin/uvx |
| 39 | + |
| 40 | +# The container runs as root (required by the nested Docker daemon), but HOME and every |
| 41 | +# tool cache/store are pinned to fixed, world-writable paths so the layout is stable |
| 42 | +# regardless of the executing UID. |
| 43 | +# |
| 44 | +# npm_config_store_dir pins pnpm's content-addressable store to a container-internal |
| 45 | +# path. Without it, pnpm places the store on the project filesystem (the bind-mounted |
| 46 | +# /workspace), which floods the host file watcher and is slow over the macOS bind mount. |
| 47 | +ENV HOME=/home/forge \ |
| 48 | + XDG_CACHE_HOME=/home/forge/.cache \ |
| 49 | + XDG_DATA_HOME=/home/forge/.local/share \ |
| 50 | + PNPM_HOME=/home/forge/.local/share/pnpm \ |
| 51 | + npm_config_cache=/home/forge/.npm \ |
| 52 | + npm_config_store_dir=/home/forge/.local/share/pnpm/store |
| 53 | +RUN useradd -m -u 1000 forge \ |
| 54 | + && mkdir -p /home/forge/.cache /home/forge/.local/share/pnpm/store /home/forge/.npm \ |
| 55 | + && chmod -R 0777 /home/forge |
| 56 | + |
| 57 | +# fallow CLI — dead-code analysis tool — installed globally with the same pnpm |
| 58 | +# setup used by the rest of the sandbox image. Binaries are linked into |
| 59 | +# /usr/local/bin so they are on PATH for arbitrary container UIDs. |
| 60 | +# |
| 61 | +# The trailing chmod is load-bearing: this global install runs as root and populates the |
| 62 | +# pnpm store (store/v10/{files,index,projects}) with root-owned 0755 dirs, AFTER the earlier |
| 63 | +# `chmod -R 0777 /home/forge`. Because the container runs as root but agent commands run as the |
| 64 | +# host UID via `docker exec --user`, that UID could not write into the root-owned store and |
| 65 | +# `pnpm install` failed with EACCES when registering the project — which previously drove the |
| 66 | +# agent to relocate the store onto the bind-mounted /workspace (huge, slow file transfers). |
| 67 | +# Re-asserting 0777 here, as the last build step that touches /home/forge, keeps the store |
| 68 | +# writable by any exec UID. Any future build step that runs pnpm as root must do the same. |
| 69 | +RUN corepack pnpm add -g fallow@2.101.0 --global-bin-dir /usr/local/bin \ |
| 70 | + && chmod -R 0777 /home/forge |
| 71 | + |
| 72 | +# Docker Engine (daemon + CLI + buildx + compose) from Docker's official apt |
| 73 | +# repository. This enables Docker-in-Docker: when the container is launched with |
| 74 | +# FORGE_DIND=1 (privileged), the entrypoint starts a nested, isolated dockerd so |
| 75 | +# loops can build and run containers for end-to-end tests. iptables is required |
| 76 | +# by dockerd for container networking; debian bookworm uses the nft backend. |
| 77 | +RUN install -m0755 -d /etc/apt/keyrings \ |
| 78 | + && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \ |
| 79 | + && chmod a+r /etc/apt/keyrings/docker.asc \ |
| 80 | + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list \ |
| 81 | + && apt-get update \ |
| 82 | + && apt-get install -y --no-install-recommends \ |
| 83 | + docker-ce \ |
| 84 | + docker-ce-cli \ |
| 85 | + containerd.io \ |
| 86 | + docker-buildx-plugin \ |
| 87 | + docker-compose-plugin \ |
| 88 | + iptables \ |
| 89 | + && rm -rf /var/lib/apt/lists/* |
| 90 | + |
| 91 | +# Allow git to operate on the bind-mounted workspace regardless of owner UID. |
| 92 | +RUN git config --system --add safe.directory '*' |
| 93 | + |
| 94 | +# Entrypoint conditionally boots the nested Docker daemon before handing off to |
| 95 | +# the container command. When FORGE_DIND is unset/0 it is a transparent passthrough, |
| 96 | +# so non-DinD loops pay no runtime cost. |
| 97 | +COPY dind-entrypoint.sh /usr/local/bin/dind-entrypoint.sh |
| 98 | +RUN chmod 0755 /usr/local/bin/dind-entrypoint.sh |
| 99 | + |
| 100 | +WORKDIR /workspace |
| 101 | +ENTRYPOINT ["/usr/local/bin/dind-entrypoint.sh"] |
| 102 | +CMD ["sleep", "infinity"] |
0 commit comments