# opencode-devbox — portable AI dev environment # Debian-based container with opencode and configurable dev tools ARG DEBIAN_VERSION=trixie-slim FROM debian:${DEBIAN_VERSION} AS base ARG TARGETARCH ARG OPENCODE_VERSION=1.14.18 LABEL maintainer="joakimp" LABEL description="Portable opencode developer container" LABEL org.opencontainers.image.source="https://gitea.jordbo.se/joakimp/opencode-devbox" # Avoid interactive prompts during build ENV DEBIAN_FRONTEND=noninteractive # ── Core system packages ───────────────────────────────────────────── RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ wget \ git \ openssh-client \ gnupg \ jq \ ripgrep \ fd-find \ tree \ less \ htop \ tmux \ make \ patch \ diffutils \ git-crypt \ age \ file \ sudo \ locales \ procps \ unzip \ gcc \ g++ \ && ln -s /usr/bin/fdfind /usr/local/bin/fd \ && rm -rf /var/lib/apt/lists/* # ── Go-compiled tools (install from GitHub to avoid CVEs in Debian's old Go builds) # gosu — privilege de-escalation (built with Go 1.24.6) ARG GOSU_VERSION=1.19 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \ curl -fsSL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${ARCH}" -o /usr/local/bin/gosu && \ chmod +x /usr/local/bin/gosu && \ gosu --version # fzf — fuzzy finder (built with Go 1.23.12) ARG FZF_VERSION=0.71.0 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \ curl -fsSL "https://github.com/junegunn/fzf/releases/download/v${FZF_VERSION}/fzf-${FZF_VERSION}-linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin fzf && \ fzf --version # git-lfs — Git Large File Storage (built with Go 1.25) ARG GIT_LFS_VERSION=3.7.1 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \ curl -fsSL "https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/git-lfs-linux-${ARCH}-v${GIT_LFS_VERSION}.tar.gz" | tar -xz -C /tmp && \ install /tmp/git-lfs-${GIT_LFS_VERSION}/git-lfs /usr/local/bin/git-lfs && \ rm -rf /tmp/git-lfs-${GIT_LFS_VERSION} && \ git lfs install --system && \ git-lfs --version # neovim — modern text editor (pre-built release from GitHub) ARG NVIM_VERSION=0.12.1 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \ curl -fsSL "https://github.com/neovim/neovim/releases/download/v${NVIM_VERSION}/nvim-linux-${ARCH}.tar.gz" | tar -xz -C /opt && \ ln -s /opt/nvim-linux-${ARCH}/bin/nvim /usr/local/bin/nvim && \ nvim --version | head -1 # bat — syntax-highlighted cat replacement ARG BAT_VERSION=0.26.1 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ curl -fsSL "https://github.com/sharkdp/bat/releases/download/v${BAT_VERSION}/bat-v${BAT_VERSION}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \ install /tmp/bat-v${BAT_VERSION}-${ARCH}-unknown-linux-musl/bat /usr/local/bin/bat && \ rm -rf /tmp/bat-v${BAT_VERSION}-* && \ bat --version # eza — modern ls replacement ARG EZA_VERSION=0.23.4 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ curl -fsSL "https://github.com/eza-community/eza/releases/download/v${EZA_VERSION}/eza_${ARCH}-unknown-linux-gnu.tar.gz" | tar -xz -C /usr/local/bin && \ eza --version | head -1 # zoxide — smarter cd command ARG ZOXIDE_VERSION=0.9.9 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ curl -fsSL "https://github.com/ajeetdsouza/zoxide/releases/download/v${ZOXIDE_VERSION}/zoxide-${ZOXIDE_VERSION}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin zoxide && \ zoxide --version # uv — fast Python package manager (replaces pip, venv, pyenv) ARG UV_VERSION=0.11.7 RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ curl -fsSL "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \ install /tmp/uv-${ARCH}-unknown-linux-musl/uv /usr/local/bin/uv && \ install /tmp/uv-${ARCH}-unknown-linux-musl/uvx /usr/local/bin/uvx && \ rm -rf /tmp/uv-* && \ uv --version # rustup — Rust toolchain manager # Installs the rustup-init binary only. Users bootstrap Rust with: # rustup-init -y && source ~/.cargo/env # Toolchains persist via devbox-rustup and devbox-cargo volumes. RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ curl -fsSL "https://static.rust-lang.org/rustup/dist/${ARCH}-unknown-linux-gnu/rustup-init" -o /usr/local/bin/rustup-init && \ chmod +x /usr/local/bin/rustup-init # Set locale — generate common UTF-8 locales (override via LANG/LC_ALL env vars) # To add more locales, run: sudo sed -i '/.UTF-8/s/^# //g' /etc/locale.gen && sudo locale-gen RUN sed -i -E '/(en_US|en_GB|sv_SE|da_DK|nb_NO|fi_FI|de_DE|fr_FR|es_ES|it_IT|pt_BR|nl_NL|pl_PL|ja_JP|ko_KR|zh_CN)\.UTF-8/s/^# //g' /etc/locale.gen && locale-gen ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en ENV LC_ALL=en_US.UTF-8 ENV EDITOR=nvim ENV PATH="/home/developer/.local/bin:/home/developer/.cargo/bin:${PATH}" # ── Node.js (required for opencode v1.x install + MCP servers) ────── ARG NODE_VERSION=22 RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \ apt-get install -y --no-install-recommends nodejs && \ rm -rf /var/lib/apt/lists/* # ── Install opencode via npm ───────────────────────────────────────── # v1.x is distributed as an npm package with platform-specific binaries RUN npm install -g opencode-ai@${OPENCODE_VERSION} && \ opencode --version # ── AWS CLI v2 (for SSO/Bedrock authentication) ───────────────────── RUN ARCH=$(case "${TARGETARCH}" in \ amd64) echo "x86_64" ;; \ arm64) echo "aarch64" ;; \ *) echo "x86_64" ;; \ esac) && \ curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip" -o /tmp/awscli.zip && \ unzip -q /tmp/awscli.zip -d /tmp && \ /tmp/aws/install && \ rm -rf /tmp/aws /tmp/awscli.zip && \ aws --version # ── Optional: Python ───────────────────────────────────────────────── ARG INSTALL_PYTHON=false RUN if [ "${INSTALL_PYTHON}" = "true" ]; then \ apt-get update && apt-get install -y --no-install-recommends \ python3 python3-pip python3-venv && \ rm -rf /var/lib/apt/lists/*; \ fi # ── Optional: Go ───────────────────────────────────────────────────── ARG INSTALL_GO=false ARG GO_VERSION=1.26.2 RUN if [ "${INSTALL_GO}" = "true" ]; then \ GOARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \ curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz" | tar -C /usr/local -xz && \ ln -s /usr/local/go/bin/go /usr/local/bin/go && \ ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \ fi # ── Optional: oh-my-opencode-slim (multi-agent orchestration) ──────── # Installs Bun runtime and the oh-my-opencode-slim npm package. # Runtime activation is controlled by ENABLE_OMOS env var in entrypoint. # Uses the baseline Bun build (SSE4.2 only) for compatibility with older # CPUs that lack AVX2 (e.g. Sandy Bridge on OpenStack). ARG INSTALL_OMOS=false ARG OMOS_VERSION=latest RUN if [ "${INSTALL_OMOS}" = "true" ]; then \ ARCH=$(uname -m) && \ if [ "$ARCH" = "x86_64" ]; then \ BUN_ARCH="x64-baseline"; \ elif [ "$ARCH" = "aarch64" ]; then \ BUN_ARCH="aarch64"; \ fi && \ curl -fsSL "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-${BUN_ARCH}.zip" -o /tmp/bun.zip && \ unzip -o /tmp/bun.zip -d /tmp/bun && \ mv /tmp/bun/bun-linux-${BUN_ARCH}/bun /usr/local/bin/bun && \ chmod +x /usr/local/bin/bun && \ rm -rf /tmp/bun /tmp/bun.zip && \ bun --version && \ npm install -g oh-my-opencode-slim@${OMOS_VERSION}; \ fi # ── Non-root user ──────────────────────────────────────────────────── ARG USER_NAME=developer ARG USER_UID=1000 ARG USER_GID=1000 RUN groupadd --gid ${USER_GID} ${USER_NAME} && \ useradd --uid ${USER_UID} --gid ${USER_GID} -m -s /bin/bash ${USER_NAME} && \ echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/${USER_NAME} # Create standard directories RUN mkdir -p /workspace \ /home/${USER_NAME}/.config/opencode/skills \ /home/${USER_NAME}/.agents/skills \ /home/${USER_NAME}/.local/share/opencode \ /home/${USER_NAME}/.ssh && \ chown -R ${USER_NAME}:${USER_NAME} /workspace /home/${USER_NAME} # ── Entrypoint ──────────────────────────────────────────────────────── COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh # Start as root — entrypoint adjusts UID/GID then drops to developer WORKDIR /workspace ENTRYPOINT ["entrypoint.sh"] CMD ["opencode"]