# syntax=docker/dockerfile:1.7 # # Two stages live here: # # - `base`: minimal image used as the `container:` for every CI job. Just # enough to run `bun install`, `bun run …`, `just`, `psql`, `rsync`, ssh. # Published as `ghcr.io/zkldi/tachi-ci`. # - `dev`: superset of `base` with all the devcontainer QoL (fish, neovim, # gh, ripgrep, fd, bat, fzf, mdbook (upstream binary), minio-client, locales, sudo, the # `tachi` user, ...). Published as `ghcr.io/zkldi/tachi-dev`. # # Always use `--no-install-recommends`. Debian's recommends-by-default pulls # the full LLVM/Mesa/Vulkan/GCC chain in via various transitive deps and adds # ~750MB of crap that nothing here actually needs. # --------------------------------------------------------------------------- # bun-src: just here so we can `COPY --from=` the binary into our stages. # # We deliberately track the same floating tag prod uses (see # `docker/Dockerfile.server`, `FROM oven/bun:alpine`). The `debian` variant # is used here because our base is `debian:13-slim` (glibc), not Alpine # (musl). If/when prod pins a specific bun version, pin this to match. # --------------------------------------------------------------------------- FROM oven/bun:debian AS bun-src # --------------------------------------------------------------------------- # base: CI image # --------------------------------------------------------------------------- FROM debian:13-slim AS base WORKDIR /tachi # Add the official PostgreSQL APT repository so we can install client tools # that match the server version (v18). The Debian-packaged postgresql-client # only provides v17 on Debian 13 (trixie). `gnupg` is only needed to dearmor # the keyring and is purged afterwards. RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates curl gnupg \ && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | gpg --dearmor -o /etc/apt/trusted.gpg.d/pgdg.gpg \ && echo "deb https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" \ > /etc/apt/sources.list.d/pgdg.list \ && apt-get update \ && apt-get install -y --no-install-recommends \ # essentials for running the workspace in CI git jq unzip just \ # vitest spawns workers via node; bun runs vitest itself but the # worker subprocesses are node. With --no-install-recommends this # pulls libnode115 but skips npm + nodejs-doc. nodejs \ # postgres client tools (psql, pg_dump, pg_restore, pg_isready, ...) postgresql-client-18 \ # rsync + ssh client for the homepage deploy job rsync openssh-client \ && apt-get purge -y gnupg \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* # bun — copied from the official oven/bun image (see BUN_VERSION arg at top). # `bunx` is a symlink to `bun` in the upstream image; we recreate it here. COPY --from=bun-src /usr/local/bin/bun /usr/local/bin/bun RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx # expose the db CLI globally RUN ln -sf /tachi/typescript/db-cli/src/index.ts /usr/local/bin/tachidb # --------------------------------------------------------------------------- # dev: devcontainer image # --------------------------------------------------------------------------- FROM base AS dev # so apt doesn't complain about the lack of a dialog-like program ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends \ # shell + editor + admin fish neovim sudo locales \ # search / files / network / parallel ripgrep fd-find bat fzf parallel wget \ # github CLI for `gh auth login`-style flows gh \ # MinIO CLI (binary is `minio-client`, not Midnight Commander's `mc`) minio-client \ # `xdg-open` so the client vite plugin can launch a browser without # crashing on missing binary (see typescript/client/vite.config.ts). xdg-utils \ && rm -rf /var/lib/apt/lists/* ENV DEBIAN_FRONTEND=readline # mdbook — installed from vendored tarballs in dev/mdbook/ rather than the # Debian apt package, which strips bundled fonts (the +ds repackaging). COPY dev/mdbook/ /tmp/mdbook/ RUN set -eu; \ case "$(uname -m)" in \ x86_64) tarball=mdbook-v0.5.3-x86_64-unknown-linux-gnu.tar.gz ;; \ aarch64) tarball=mdbook-v0.5.3-aarch64-unknown-linux-musl.tar.gz ;; \ *) echo "Unsupported arch: $(uname -m)" >&2; exit 1 ;; \ esac; \ tar -xz -C /usr/local/bin -f "/tmp/mdbook/${tarball}" \ && rm -rf /tmp/mdbook # `fd` is called `fdfind` on debian. Awesome. RUN ln -s "$(which fdfind)" /usr/bin/fd # setup locales # https://stackoverflow.com/questions/28405902/how-to-set-the-locale-inside-a-debian-ubuntu-docker-container RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 # https://github.com/python-babel/babel/issues/990 # it wouldn't be python without needing absurd global state manipulation to fix # an incoherent error message RUN rm -f /etc/localtime && ln -s /usr/share/zoneinfo/Etc/UTC /etc/localtime # Fix locale issue perl repeatedly complains about RUN echo "LC_ALL=en_US.UTF-8\nLANG=en_US.UTF-8" > /etc/default/locale # create tachi user and give them sudo RUN addgroup --gid 1000 tachi \ && adduser --disabled-password --uid 1000 --gid 1000 --home /home/tachi tachi \ && echo "tachi ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers # Make the /tachi working directory owned by our user instead of root. Docker volumes # are mounted as root UNLESS the folder already exists inside the host and has non-root # ownership. This is the only way to declare a volume in docker has non-root ownership. # Unbelievably obscure. RUN chown -R tachi:tachi /tachi USER tachi # see above comment about non-root volumes RUN mkdir node_modules # keep container alive indefinitely # Exclude .git from chown to avoid permission errors on macOS Docker mounts # TODO(zk): What the fuck is this? ENTRYPOINT ["sh", "-c", "sudo find /tachi -maxdepth 1 ! -name '.git' ! -name '/tachi' -exec chown -R tachi:tachi {} + 2>/dev/null || true; exec /bin/fish"]