- Dockerfile 68.9%
- Shell 31.1%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| asset | ||
| scripts | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
Sindri
A flexible, multi-stage Docker image providing minimal to complete development environments. Choose the variant that fits your needs: from a lightweight base image to a full-featured development workspace.
Image Variants
Sindri provides three image variants built from a common base:
🔹 ci-* - CI/CD Ready
Lightweight image with Node.js LTS for continuous integration pipelines.
Features:
- Ubuntu 24.04 base with essential system utilities
- Core utilities (curl, wget, git, jq, nano, etc.)
- Network tools (ssh, rsync, netcat, etc.)
- Compression tools (zip, tar, gzip, xz, brotli, etc.)
- Build essentials (make, autoconf, pkg-config, etc.)
- Azul Zulu JDK, configurable at build time
- Node.js LTS (via NodeSource)
- npm and yarn
- Timezone configured to Europe/Paris
- Working directory set to
/workspaces
Use case: Ideal for GitHub Actions, Forgejo Actions, GitLab CI, or any CI/CD pipeline requiring Node.js.
Available as: git.van-hemmen.com/actions/sindri:ci-latest, git.van-hemmen.com/actions/sindri:ci-26.8.1
docker pull git.van-hemmen.com/actions/sindri:ci-latest
docker run -it git.van-hemmen.com/actions/sindri:ci-latest
🔹 coder-* - Full Development Environment
Complete dev workspace with the coder user; per-user runtime is provisioned at workspace start by
scripts/coder-init.sh so the same image works whether the PVC mounts at
/workspaces or at /home/coder.
Features:
- Everything from the base layer
- Non-root
coderuser with passwordless sudo coderuser created with UID/GID1000by default- Default project directory set to
/home/coder/Projects scripts/coder-init.shprovisions, at workspace start: customized bash prompt and environment, NVM with Node.js 24 ( configurable), Yarn, and a global gitignore from toptal.com- Ready for VS Code Remote Containers, Coder, or similar
Use case: Full-featured development environment for remote coding, devcontainers, or local development.
Available as: git.van-hemmen.com/actions/sindri:coder-latest, git.van-hemmen.com/actions/sindri:coder-26.8.1
docker pull git.van-hemmen.com/actions/sindri:coder-latest
docker run -it git.van-hemmen.com/actions/sindri:coder-latest
🔹 coder-xfce-vnc-* - Web Desktop Development Environment
Desktop-enabled workspace based on the coder variant, with XFCE and noVNC.
Features:
- Everything from the
codervariant - XFCE desktop environment
- TigerVNC server (loopback only) + websockify on port
6080 - noVNC web client with a path-aware redirect (works under Coder's path-based app proxy without wildcard DNS — see Web access patterns below)
- Pre-installed GUI applications:
- Firefox — from Mozilla's official APT repo (not the Ubuntu Snap stub)
- JetBrains Toolbox — installed to
/opt/jetbrains-toolbox, symlinked into/usr/local/bin, with a system-wide XDG menu entry pre-shipped
- Per-user desktop bootstrap via
scripts/coder-init-desktop.sh(heals the XFCE preferred-browser setting when the PVC carries stale state) - Runtime env vars exposed for tuning:
DISPLAY,VNC_PORT(5901),NOVNC_PORT(6080),VNC_GEOMETRY(1920x1080),VNC_DEPTH(24),VNC_PASSWORD(empty by default)
Use case: Remote desktop development environment for Coder workspaces, browser-based desktop access, or any tooling that needs a real graphical session (JetBrains IDEs running locally, Electron tools, browser automation, etc.).
Available as: git.van-hemmen.com/actions/sindri:coder-xfce-vnc-latest,
git.van-hemmen.com/actions/sindri:coder-xfce-vnc-26.8.1
docker pull git.van-hemmen.com/actions/sindri:coder-xfce-vnc-latest
docker run -it -p 6080:6080 git.van-hemmen.com/actions/sindri:coder-xfce-vnc-latest
# Open http://localhost:6080/ — the index page auto-redirects to the noVNC client.
Usage
Quick Start
# Pull and run the CI variant
docker pull git.van-hemmen.com/actions/sindri:ci-latest
docker run -it git.van-hemmen.com/actions/sindri:ci-latest
# Pull and run the coder variant
docker pull git.van-hemmen.com/actions/sindri:coder-latest
docker run -it git.van-hemmen.com/actions/sindri:coder-latest
# Pull and run the XFCE/noVNC coder variant
docker pull git.van-hemmen.com/actions/sindri:coder-xfce-vnc-latest
docker run -it -p 6080:6080 git.van-hemmen.com/actions/sindri:coder-xfce-vnc-latest
Building with Custom Arguments
The image supports build arguments:
docker build --target coder \
--build-arg ARG_TZ="America/New_York" \
--build-arg AZUL_JAVA_MAJOR=21 \
-t sindri:coder-custom .
Available arguments:
UBUNTU_VERSION: Ubuntu base image version (default:24.04)ARG_TZ: Timezone (default:Europe/Paris)AZUL_JAVA_MAJOR: Azul Zulu Java major version (default:21)USER_NAME: Workspace user name (default:coder)USER_UID: Workspace user UID (default:1000)USER_GID: Workspace user GID (default:1000)PROJECTS_DIR: Default project directory for thecodervariants (default:/home/coder/Projects)
Node.js version and global-gitignore URL are no longer build-time arguments. They are configured at workspace start via environment variables consumed by
scripts/coder-init.sh.
Using with Forgejo/GitHub Actions
Use the CI variant in your workflow:
jobs:
build:
runs-on: ubuntu-latest
container: git.van-hemmen.com/actions/sindri:ci-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test
Using with VS Code Remote Containers
Create .devcontainer/devcontainer.json:
{
"name": "Sindri Development Environment",
"build": {
"dockerfile": "../Dockerfile",
"target": "coder"
},
"remoteUser": "coder",
"workspaceFolder": "/home/coder/Projects"
}
Or reference the remote image:
{
"name": "Sindri Development Environment",
"image": "git.van-hemmen.com/actions/sindri:coder-latest",
"remoteUser": "coder",
"workspaceFolder": "/home/coder/Projects"
}
Extending the Image
Build on top of any variant:
FROM git.van-hemmen.com/actions/sindri:ci-latest
# Add your custom tools
RUN apt-get update && apt-get install -y \
postgresql-client \
redis-tools \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /myapp
Workspace Initialization (coder-init)
The coder-* variants no longer bake the per-user home directory contents into image layers, so the same image can be
deployed in either of the two Coder workspace modes:
- Devcontainer mode — the PVC mounts at
/workspaces, leaving$HOME(/home/coder) intact. - Plain Kubernetes pod mode — the PVC mounts at
/home/coderand shadows anything baked into image layers.
To populate $HOME consistently in both modes, the script scripts/coder-init.sh (shipped in the image as
/usr/local/bin/coder-init) performs the per-user setup at workspace start. It is idempotent and safe to re-run.
What it does (as the workspace user):
- Seeds
~/.bashrcfrom/etc/skel/.bashrcwhen the home directory is empty (fresh PVC). - Appends a managed block to
~/.bashrcwith the NVM init lines and the customizedPS1. - Downloads a global gitignore template and points
git config --global core.excludesfileat it. - Installs NVM in
~/.nvm(if missing). - Installs the configured Node.js major version via NVM, marks it as
default, and installs Yarn globally.
Configuration (environment variables):
| Variable | Default | Description |
|---|---|---|
NODE_MAJOR |
24 |
Node.js major version installed via NVM. |
NVM_VERSION |
0.40.1 |
NVM installer version. |
GITIGNORE_URL |
https://www.toptal.com/developers/gitignore/api/linux,jetbrains+all,visualstudio,visualstudiocode |
Global gitignore template URL. |
Usage from a Coder template (in the workspace's startup_script):
# Use the defaults baked in the script
coder-init
# Or pin a Node.js version for this workspace
NODE_MAJOR=22 coder-init
Usage from a devcontainer, in .devcontainer/devcontainer.json:
{
"image": "git.van-hemmen.com/actions/sindri:coder-latest",
"remoteUser": "coder",
"workspaceFolder": "/home/coder/Projects",
"postCreateCommand": "coder-init"
}
The script refuses to run as root.
Desktop Bootstrap (coder-init-desktop)
Companion to coder-init, shipped only in the coder-xfce-vnc variant at /usr/local/bin/coder-init-desktop. Same
contract: run as the workspace user, once per workspace start, idempotent. Refuses to run as root.
It covers per-user setup that doesn't make sense on the headless coder variant — currently:
- Heals XFCE's preferred web browser when
~/.config/xfce4/helpers.rcis missing or points at a binary that no longer exists (common after a PVC carries over from an older image). WritesWebBrowser=firefoxonly when the existing value is invalid; never trampling a user who has deliberately chosen something else.
Usage from a Coder template — guard with command -v so the same template stays compatible with the headless
coder variant:
/usr/local/bin/coder-init
if command -v coder-init-desktop >/dev/null 2>&1; then
/usr/local/bin/coder-init-desktop
fi
if command -v start-xfce-vnc >/dev/null 2>&1; then
nohup /usr/local/bin/start-xfce-vnc >/tmp/xfce-vnc.log 2>&1 &
fi
start-xfce-vnc runs tigervncserver + websockify and exec's into websockify, so it must be backgrounded from
startup_script. It is also idempotent — re-invoking when port 6080 is already bound short-circuits with a friendly
message rather than tearing the live session down.
Web Access Patterns
The coder-xfce-vnc variant is designed to work behind Coder's path-based app proxy (subdomain = false), which is
the only option on a Coder OSS deployment without wildcard DNS. Two subtleties this image already handles:
-
/returns a redirect, not 404. The Ubuntunovncpackage shipsvnc.htmlbut noindex.html. We install a tiny JS-basedindex.htmlat/usr/share/novnc/index.htmlthat readswindow.location.pathname, strips the filename, and redirects tovnc.html?autoconnect=1&resize=remote&path=<dir>/websockify. This meanscoder_app.url = "http://localhost:6080"(no path component) is sufficient — Coder lands the browser at the slug root, the script bounces it to the real entry point, and relative asset paths resolve correctly. -
The noVNC WebSocket goes under the slug, not to origin root. noVNC's
pathsetting defaults to the literal string"websockify", not derived fromwindow.location— so under any path-based proxy it would otherwise openwss://<host>/websockify(origin root) and miss the prefix entirely. The redirect script computes the correct path from the current URL and passes it via the?path=query parameter.
If your Coder deployment has a Traefik / nginx / Envoy reverse proxy in front of it, make sure it forwards WebSocket
Upgrade: headers unaltered — that's a deployment concern, not an image concern.
Software Rendering & Desktop Performance
The image targets non-privileged Kubernetes pods with no GPU and no /dev/dri, so all rendering goes through Mesa's
llvmpipe software rasterizer. The image is configured for this from the start:
LIBGL_ALWAYS_SOFTWARE=1is set as anENVin the stage. Mesa skips the always-failing DRI probe and goes straight to llvmpipe. This shaves a noticeable beat off the startup of every GL app (browsers, IDEs, Chromium-based UIs).libgbm1is installed. Without it, Chromium/JCEF apps log "cannot create linux GL context" and fall through a slow error path. Installing it is the single biggest fix for the GL warning seen in JetBrains IDEs and Toolbox launches.mesa-utilsshipsglxinfoso you can verify post-launch:glxinfo -B # OpenGL renderer string: llvmpipe (LLVM 20.x.x, 256 bits)
JetBrains Toolbox (2.x) specifics
Toolbox 2.x is a Compose Multiplatform + Skia application (not Chromium / not JCEF, despite earlier versions). A few non-obvious implications:
--disable-gpuis a no-op for Toolbox 2.x — it's a Chromium flag and Toolbox no longer uses Chromium. We pass it in the shipped.desktopentry anyway as a defensive hint for downstream tooling that scans Exec lines; it doesn't hurt and is silently ignored by Toolbox itself.- No CLI option disables the animated gradient background. The only switch is the in-UI toggle: ☰ Settings → "Use
background effects" (introduced in Toolbox 2.4). The setting persists to
~/.local/share/JetBrains/Toolbox/.settings.json(PVC-backed; survives workspace rerolls). The exact JSON key is undocumented. SKIKO_RENDER_API=SOFTWAREis an environment variable that can help. It tells Skiko to skip the OpenGL backend and rasterize Skia directly to CPU, removing one indirection (Skiko → OpenGL → llvmpipe → CPUbecomesSkiko → CPU). It is not set by default because results vary; try setting it for Toolbox if the UI feels sluggish:env SKIKO_RENDER_API=SOFTWARE /opt/jetbrains-toolbox/bin/jetbrains-toolbox- For each JetBrains IDE installed via Toolbox, after first launch open Help → Edit Custom VM Options and add:
The first uses Java2D's hand-tuned software path instead of routing Java2D through OpenGL→llvmpipe. The second stops JCEF (the Chromium engine the IDEs do still embed for the welcome screen, Markdown preview, AI Assistant, etc.) from compositing on a non-existent GPU. Both settings persist per-IDE in the PVC.-Dsun.java2d.opengl=false -Dide.browser.jcef.gpu.disable=true
State that persists across workspace rerolls
The PVC mounts at /home/coder, so the following are preserved automatically:
| App | State location |
|---|---|
| Firefox | ~/.mozilla/firefox/ (profiles, add-ons, bookmarks, passwords) |
| JetBrains Toolbox | ~/.local/share/JetBrains/Toolbox/, ~/.config/JetBrains/ |
| XFCE | ~/.config/xfce4/ (panel layout, preferred apps, etc.) |
| TigerVNC | ~/.vnc/ (xstartup, password if set, session logs) |
Building Locally
# Build CI variant
docker build --target ci -t git.van-hemmen.com/actions/sindri:ci-dev .
# Build coder variant
docker build --target coder -t git.van-hemmen.com/actions/sindri:coder-dev .
# Build XFCE/noVNC coder variant
docker build --target coder-xfce-vnc -t git.van-hemmen.com/actions/sindri:coder-xfce-vnc-dev .
# Build coder variant with a custom Java version
docker build --target coder \
--build-arg AZUL_JAVA_MAJOR=17 \
-t git.van-hemmen.com/actions/sindri:coder-java17 .
CI/CD Workflows
Forgejo Actions workflows automate multi-target builds:
- docker-dev.yaml: Builds all targets on branch commits
- docker-tag.yaml: Builds and publishes versioned releases on tags
The workflows use the KANIKO_TARGET variable to build each variant:
strategy:
matrix:
target: [ ci, coder, coder-xfce-vnc ]
env:
KANIKO_TARGET: ${{ matrix.target }}
Choosing the Right Variant
| Use Case | Recommended Variant |
|---|---|
| CI/CD pipeline with Node.js | ci-* |
| GitHub/Forgejo Actions | ci-* |
| Custom base for your own image | ci-* |
| VS Code Remote Containers | coder-* |
| Coder.com workspace | coder-* |
| GitHub Codespaces | coder-* |
| Local Node.js development | coder-* |
| Browser-based Linux desktop workspace | coder-xfce-vnc-* |
| Graphical tools in a remote workspace | coder-xfce-vnc-* |
| JetBrains IDEs running inside the workspace (not Gateway) | coder-xfce-vnc-* |
| Firefox in a remote workspace | coder-xfce-vnc-* |
Version Information
- Base OS: Ubuntu 24.04 LTS
- Java: Azul Zulu JDK, configurable at build time, default
21 - Node.js: LTS in the
civariant; installed via NVM at workspace start in thecodervariants byscripts/coder-init.sh(default Node24) - Default Timezone: Europe/Paris, configurable
- Coder User:
coder, UID/GID1000by default - Coder Project Directory:
/home/coder/Projects - noVNC Port:
6080(websockify, all interfaces) in thecoder-xfce-vncvariant - VNC Port:
5901(loopback only) in thecoder-xfce-vncvariant - Default Desktop Resolution:
1920x1080× 24-bit (override withVNC_GEOMETRY/VNC_DEPTH) - GUI Apps (xfce-vnc variant): Firefox (Mozilla deb), JetBrains Toolbox (
/opt/jetbrains-toolbox)
License
Apache License 2.0 - See LICENSE file for details.
Contributing
See CONTRIBUTING.md for guidelines on how to contribute to this project.
Code of Conduct
This project follows a Code of Conduct. Please read CODE_OF_CONDUCT.md before contributing.
