# syntax=docker/dockerfile:1 ARG BASE_IMAGE=ubuntu:25.10 FROM $BASE_IMAGE # Install basics omitted from the minimal Ubuntu server build. RUN apt-get update && apt-get upgrade -y && apt-get install -y unminimize RUN yes | unminimize # Install frequently used software dependencies and basics like Git and Fish. RUN apt-get install -y gpg sudo wget curl build-essential software-properties-common libssl-dev pkg-config libglew-dev git fish # Install handy developer tools. RUN apt-get install -y iputils-ping postgresql-client git-delta golang jq sqlite3 vim pipx ripgrep openssh-server # Install mise-en-place for project (and in some cases global) tooling management. RUN install -dm 755 /etc/apt/keyrings RUN wget -qO - https://mise.jdx.dev/gpg-key.pub | gpg --dearmor | tee /etc/apt/keyrings/mise-archive-keyring.gpg 1> /dev/null RUN echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.gpg arch=arm64] https://mise.jdx.dev/deb stable main" \ | tee /etc/apt/sources.list.d/mise.list RUN apt-get update && apt-get install -y mise ARG USERNAME=developer RUN if [ -z $USERNAME ]; then exit 1; fi # Initialize non-root user account, with passwordless sudo. RUN useradd -m -g sudo -s /usr/bin/fish $USERNAME RUN sed -i'' 's/%sudo\tALL=(ALL:ALL) ALL/%sudo ALL = (ALL) NOPASSWD: ALL/g' /etc/sudoers WORKDIR /home/$USERNAME USER $USERNAME # Activate mise for non-root user's shell sessions. RUN mkdir -p ~/.config/fish && echo 'mise activate fish | source' >> ~/.config/fish/config.fish && mkdir -p ~/.config/mise COPY --chown=$USERNAME ./assets/mise/config.toml /home/$USERNAME/.config/mise/config.toml # Install stable Rust toolchain globally using mise (rustup management is automated). RUN mise install # Install sqlx CLI for managing database migrations in sqlx projects. RUN mise x -- cargo binstall -y cargo-nextest sqlx-cli # Finish Go setup. RUN go install golang.org/x/tools/gopls@latest RUN fish -c 'fish_add_path /home/$USERNAME/go/bin' # Install Claude Code CLI. RUN curl -fsSL https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/bootstrap.sh | bash RUN fish -c 'fish_add_path /home/$USERNAME/.local/bin' COPY --chown=$USERNAME ./assets/claude/settings.json /home/$USERNAME/.claude/settings.json # Copy Helix configurations. # It seems there's a bug in `apple/container`'s COPY implementation that fails # to recursively copy directory contents from the host, so we point it to each # file. RUN mkdir -p ~/.config/helix/snippets && mkdir -p ~/.config/helix/themes COPY --chown=$USERNAME ./assets/helix/*.toml /home/$USERNAME/.config/helix/ COPY --chown=$USERNAME ./assets/helix/ignore /home/$USERNAME/.config/helix/ COPY --chown=$USERNAME ./assets/helix/snippets/*.toml /home/$USERNAME/.config/helix/snippets/ COPY --chown=$USERNAME ./assets/helix/themes/*.toml /home/$USERNAME/.config/helix/themes/ ARG VCS_EMAIL ARG VCS_NAME # Copy Jujutsu configuration. RUN mkdir -p ~/.config/jj COPY --chown=$USERNAME ./assets/jj/config.toml /home/$USERNAME/.config/jj/ RUN sed -i'' "s/{{ VCS_EMAIL }}/$VCS_EMAIL/" ~/.config/jj/config.toml RUN sed -i'' "s/{{ VCS_NAME }}/$VCS_NAME/" ~/.config/jj/config.toml COPY --chown=$USERNAME ./assets/git/gitconfig /home/$USERNAME/.gitconfig RUN sed -i'' "s/{{ VCS_EMAIL }}/$VCS_EMAIL/" ~/.gitconfig RUN sed -i'' "s/{{ VCS_NAME }}/$VCS_NAME/" ~/.gitconfig # Delete VCS config if values not provided. RUN if [ -z "$VCS_EMAIL" ] || [ -z "$VCS_NAME" ]; then rm ~/.gitconfig ~/.config/jj/config.toml; fi # Configure delta diff viewer. ENV DELTA_FEATURES='side-by-side navigate' # Configure terminal. ENV COLORTERM=truecolor ENV SHELL=fish # Pop the user into the Fish shell by default. CMD /usr/bin/fish