Files
Tam Nguyen Duc 2dabb93a78 Run the container as root so a bind-mounted /out works (issue #7) (#53)
The image dropped to a fixed non-root user (uid 10001) and pointed HOME at
/out. On native Linux Docker a bind-mounted /out is owned by whoever created
it on the host, so uid 10001 cannot write into it. Two things then failed:
kage's output and resume state under $HOME/data/kage hit "mkdir /out:
permission denied", and Chrome launched chrome_crashpad_handler with an empty
crash database path, which aborts the whole browser with
"chrome_crashpad_handler: --database is required" and fails every render.

The earlier attempt set HOME=/out, but that only helps when /out is writable,
which it is not for a non-root uid against a host-owned mount. The crash-reporter
flags in the launcher did not help either: they do not stop Chrome from spawning
the handler, so the abort stayed.

Run as root instead. Container root writes a host-owned bind mount whatever its
ownership, so both /out and HOME stay writable and the documented one-liner just
works. This does not loosen the sandbox: Chrome's sandbox is already off inside
any container (kage drops it on container detection), so root here changes
nothing that was holding.

Verified end to end in an Alpine + chromium container: the non-root image
reproduces both the crashpad abort and the permission-denied exactly as
reported, and the root image clones example.com cleanly, writing index.html and
resume state into a host-owned mounted volume.
2026-06-23 15:20:31 +07:00

48 lines
2.1 KiB
Docker

# Consumed by GoReleaser: it copies the already cross-compiled binary out of the
# build context rather than compiling, so the image build is fast and uses the
# same static binary every other artifact ships.
#
# kage always drives a real headless Chrome, so unlike a plain CLI image this one
# bundles Chromium. KAGE_CHROME points kage at the system binary so it never
# tries to download its own.
#
# GoReleaser builds one multi-platform image with buildx and stages each
# platform's binary under a $TARGETPLATFORM directory (e.g. linux/amd64/) in the
# build context, so the COPY line selects the right one through the automatic
# TARGETPLATFORM build arg.
FROM alpine:3.21
ARG TARGETPLATFORM
# chromium for rendering; ca-certificates for HTTPS; tzdata for sane timestamps;
# the font package so rendered pages have glyphs to lay out.
RUN apk add --no-cache chromium ca-certificates tzdata font-noto \
&& mkdir -p /out
COPY $TARGETPLATFORM/kage /usr/bin/kage
WORKDIR /out
# Point kage at the bundled Chromium and write mirrors under /out by default:
#
# docker run -v "$PWD/out:/out" ghcr.io/tamnd/kage clone example.com
#
# The container runs as root, and that is deliberate (issue #7). A bind-mounted
# /out is owned by whoever created it on the host, so only root can reliably
# write into it; a fixed non-root uid cannot, and both kage's output and resume
# state (under $HOME/data/kage) then fail with "mkdir /out: permission denied".
# The same unwritable HOME also breaks Chrome: it launches chrome_crashpad_handler
# with an empty crash database path, which aborts the whole browser with
# "chrome_crashpad_handler: --database is required" and fails every render.
# Running as root keeps /out and HOME writable whatever the host owns, so the
# one-liner above just works. This costs nothing in the sandbox: Chrome's sandbox
# is already off inside any container (kage drops it on container detection), so
# root here does not loosen a boundary that was holding. HOME points at /out so
# the default output and Chrome's writable state both land in the mounted volume.
ENV KAGE_CHROME=/usr/bin/chromium-browser \
HOME=/out
VOLUME ["/out"]
ENTRYPOINT ["/usr/bin/kage"]