feat(docker): ship gh, tailscale and cloudflared in the image
Operator asked for cloudflared, tailscale, rg, git and gh available by default in the container. git/ca-certificates/ripgrep already landed; this adds the remaining three. Each comes from its vendor's own signed apt repository rather than a curl-to-shell installer, so signature checking and upgrades follow the normal apt path: gh https://cli.github.com/packages tailscale https://pkgs.tailscale.com/stable/debian cloudflared https://pkg.cloudflare.com/cloudflared Why each belongs in the image: gh backs Fusion's gh-cli GitHub auth mode (the auth route instructs operators to run `gh auth login`, impossible without the binary), cloudflared backs the dashboard's remote-access feature whose in-app installer cannot bootstrap itself reliably in a slim container, and tailscale is the private-network option for the same box. Installing tailscale does NOT make tailscaled runnable by itself: the daemon also needs --cap-add NET_ADMIN --device /dev/net/tun at docker run. Shipping the binary is the image's part; granting kernel capabilities stays an explicit operator decision. Commands were validated live in a running container before being written here; the guard test asserts both the repo wiring and the package names so half a change cannot silently ship. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/docker-operator-tooling.md
Normal file
7
.changeset/docker-operator-tooling.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: The Docker image now ships gh, tailscale, and cloudflared alongside git and ripgrep.
|
||||
category: feature
|
||||
dev: Runner stage adds the GitHub CLI (backs `githubAuthMode: "gh-cli"`, which the auth route tells operators to set up with `gh auth login`), cloudflared (backs dashboard remote access, whose in-app installer cannot bootstrap itself reliably in a slim container), and tailscale, each from its vendor's signed apt repository rather than a curl-to-shell installer. Installing tailscale does not make `tailscaled` runnable on its own — that still needs `--cap-add NET_ADMIN --device /dev/net/tun` at `docker run`. Package names and repo URLs are asserted in scripts/__tests__/dockerfile-workspace-manifests.test.mjs.
|
||||
36
Dockerfile
36
Dockerfile
@@ -78,7 +78,41 @@ ENV PORT=4040
|
||||
# reach for `rg` as their primary search tool; without it they silently degrade to slower/partial
|
||||
# fallbacks inside the container while working fine on a developer machine that has it installed.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git ca-certificates ripgrep \
|
||||
&& apt-get install -y --no-install-recommends git ca-certificates ripgrep curl gnupg \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# FNXC:DockerRun 2026-08-18-06:40: gh, tailscale, and cloudflared ship in the image.
|
||||
# Rationale per tool: `gh` backs Fusion's GitHub integration (githubAuthMode "gh-cli" is a documented
|
||||
# option and the auth route tells operators to run `gh auth login`, which is impossible if the binary
|
||||
# is absent); `cloudflared` backs the dashboard's remote-access feature, whose installer cannot
|
||||
# bootstrap itself reliably inside a slim container; `tailscale` gives the same box a private-network
|
||||
# option. All three come from their vendors' own apt repositories with signed keyrings rather than
|
||||
# curl-to-shell installers, so upgrades and signature checks follow the normal apt path.
|
||||
#
|
||||
# NOTE: installing tailscale does NOT make `tailscaled` runnable by itself — the daemon additionally
|
||||
# needs `--cap-add NET_ADMIN --device /dev/net/tun` on `docker run`. Shipping the binary is the part
|
||||
# the image can own; granting kernel capabilities stays an explicit operator decision.
|
||||
#
|
||||
# External integration evidence:
|
||||
# gh — repo https://github.com/cli/cli, docs https://cli.github.com/,
|
||||
# apt https://cli.github.com/packages, binary `gh`, key
|
||||
# githubcli-archive-keyring.gpg (vendor-signed; upstream-pending-verification)
|
||||
# tailscale — repo https://github.com/tailscale/tailscale, docs https://tailscale.com/download/linux,
|
||||
# apt https://pkgs.tailscale.com/stable/debian, binaries `tailscale`/`tailscaled`,
|
||||
# key bookworm.noarmor.gpg (vendor-signed; upstream-pending-verification)
|
||||
# cloudflared — repo https://github.com/cloudflare/cloudflared, docs https://pkg.cloudflare.com/,
|
||||
# apt https://pkg.cloudflare.com/cloudflared, binary `cloudflared`,
|
||||
# key cloudflare-main.gpg (vendor-signed; upstream-pending-verification)
|
||||
RUN install -m 0755 -d /etc/apt/keyrings \
|
||||
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \
|
||||
&& curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg -o /usr/share/keyrings/tailscale-archive-keyring.gpg \
|
||||
&& curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list -o /etc/apt/sources.list.d/tailscale.list \
|
||||
&& curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg -o /usr/share/keyrings/cloudflare-main.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main" > /etc/apt/sources.list.d/cloudflared.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends gh tailscale cloudflared \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.33.0 --activate
|
||||
|
||||
@@ -132,3 +132,25 @@ test("runner stage installs ca-certificates alongside git", () => {
|
||||
"runner stage must install ripgrep — the coding agents Fusion drives use `rg` as their primary search tool",
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:DockerRun 2026-08-18-06:40:
|
||||
The operator tooling the image promises must actually be in it. `gh` backs the gh-cli GitHub auth
|
||||
mode, `cloudflared` backs remote access, and `tailscale` is the private-network option; each is
|
||||
installed from its vendor's signed apt repository. Assert the repo wiring AND the package names, so
|
||||
dropping either half (a keyring without the install, or an install whose repo line was removed) fails
|
||||
here instead of at first use inside a container.
|
||||
*/
|
||||
test("runner stage installs gh, tailscale and cloudflared from vendor repositories", () => {
|
||||
const dockerfile = readFileSync(path.join(repoRoot, "Dockerfile"), "utf8");
|
||||
const runnerStage = dockerfile.slice(dockerfile.indexOf("FROM node:22-slim AS runner"));
|
||||
|
||||
for (const [tool, repo] of [
|
||||
["gh", "https://cli.github.com/packages"],
|
||||
["tailscale", "https://pkgs.tailscale.com/stable/debian"],
|
||||
["cloudflared", "https://pkg.cloudflare.com/cloudflared"],
|
||||
]) {
|
||||
assert.ok(runnerStage.includes(repo), `runner stage must configure the ${tool} apt repository (${repo})`);
|
||||
assert.match(runnerStage, new RegExp(`apt-get install[^\n]*(?:\\\n[^\n]*)*\\b${tool}\\b`), `runner stage must install ${tool}`);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user