c05ec7503c
Bump OPENCODE_VERSION ARG from 1.14.19 to 1.14.20 to track the new
upstream release on npm.
Clarify the tagging convention in AGENTS.md: the first build on a new
opencode version uses the bare 'v{opencode_version}' tag (no letter
suffix). Letter suffixes (a, b, c, ...) are reserved for container-
level rebuilds on the same opencode version (CVE fixes, doc changes,
entrypoint bugs). The previous wording implied a letter was always
required, which was never the actual behaviour.
230 lines
11 KiB
Docker
230 lines
11 KiB
Docker
# 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.20
|
|
|
|
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++ \
|
|
rsync \
|
|
&& 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 '/<locale>.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}/.cache/bash \
|
|
/home/${USER_NAME}/.ssh && \
|
|
chown -R ${USER_NAME}:${USER_NAME} /workspace /home/${USER_NAME}
|
|
|
|
# ── Shell defaults (bash history, aliases, readline) ─────────────────
|
|
# Shipped under /etc/skel-devbox/ rather than copied directly to the
|
|
# user's home. The entrypoint copies them to /home/developer/ only if
|
|
# the target file does not already exist, so host bind-mounts and
|
|
# previously-customized files are never overwritten. Users can restore
|
|
# the baked defaults anytime via:
|
|
# cp /etc/skel-devbox/.bash_aliases ~/.bash_aliases
|
|
# History itself persists via the devbox-shell-history named volume
|
|
# mounted at ~/.cache/bash (HISTFILE points there).
|
|
RUN mkdir -p /etc/skel-devbox
|
|
COPY rootfs/home/developer/.bash_aliases /etc/skel-devbox/.bash_aliases
|
|
COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc
|
|
|
|
# ── 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"]
|