A curated base container image providing a consistent runtime for Forgejo pipelines and coder development containers. https://git.van-hemmen.com/actions/sindri
  • Dockerfile 68.9%
  • Shell 31.1%
Find a file
GuillaumeHemmen e33cf43cf5
All checks were successful
/ docker-tag-ci (push) Successful in 3m16s
/ docker-tag-coder (push) Successful in 3m50s
/ docker-tag-coder-xfce-vnc (push) Successful in 6m1s
#7 - Adding additional dev tool in the base image (#8)
Closes #7

Reviewed-on: #8
2026-06-04 07:01:51 +00:00
.forgejo/workflows #5 - Add coder-xfce-vnc image variant and move user setup to runtime (#6) 2026-05-30 07:23:03 +00:00
asset #0000 - Add essential project documentation and license files 2026-02-15 21:06:38 +01:00
scripts #0 - Update coder-init.sh to seed .profile alongside .bashrc for compatibility with login shells 2026-05-30 08:33:36 +00:00
CODE_OF_CONDUCT.md #0000 - Add essential project documentation and license files 2026-02-15 21:06:38 +01:00
CONTRIBUTING.md #0000 - Add essential project documentation and license files 2026-02-15 21:06:38 +01:00
Dockerfile #7 - Adding additional dev tool in the base image (#8) 2026-06-04 07:01:51 +00:00
LICENSE #0000 - Add essential project documentation and license files 2026-02-15 21:06:38 +01:00
README.md #5 - Add coder-xfce-vnc image variant and move user setup to runtime (#6) 2026-05-30 07:23:03 +00:00

sindri.png

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 coder user with passwordless sudo
  • coder user created with UID/GID 1000 by default
  • Default project directory set to /home/coder/Projects
  • scripts/coder-init.sh provisions, 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 coder variant
  • 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 the coder variants (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/coder and 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 ~/.bashrc from /etc/skel/.bashrc when the home directory is empty (fresh PVC).
  • Appends a managed block to ~/.bashrc with the NVM init lines and the customized PS1.
  • Downloads a global gitignore template and points git config --global core.excludesfile at 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.rc is missing or points at a binary that no longer exists (common after a PVC carries over from an older image). Writes WebBrowser=firefox only 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:

  1. / returns a redirect, not 404. The Ubuntu novnc package ships vnc.html but no index.html. We install a tiny JS-based index.html at /usr/share/novnc/index.html that reads window.location.pathname, strips the filename, and redirects to vnc.html?autoconnect=1&resize=remote&path=<dir>/websockify. This means coder_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.

  2. The noVNC WebSocket goes under the slug, not to origin root. noVNC's path setting defaults to the literal string "websockify", not derived from window.location — so under any path-based proxy it would otherwise open wss://<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=1 is set as an ENV in 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).
  • libgbm1 is 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-utils ships glxinfo so 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-gpu is a no-op for Toolbox 2.x — it's a Chromium flag and Toolbox no longer uses Chromium. We pass it in the shipped .desktop entry 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=SOFTWARE is 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 → CPU becomes Skiko → 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:
    -Dsun.java2d.opengl=false
    -Dide.browser.jcef.gpu.disable=true
    
    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.

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 ci variant; installed via NVM at workspace start in the coder variants by scripts/coder-init.sh (default Node 24)
  • Default Timezone: Europe/Paris, configurable
  • Coder User: coder, UID/GID 1000 by default
  • Coder Project Directory: /home/coder/Projects
  • noVNC Port: 6080 (websockify, all interfaces) in the coder-xfce-vnc variant
  • VNC Port: 5901 (loopback only) in the coder-xfce-vnc variant
  • Default Desktop Resolution: 1920x1080 × 24-bit (override with VNC_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.