11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
workspaces-core-images produces the base ("core") Docker images from which every other Kasm Workspaces image is derived. These images bundle a Linux desktop environment, a VNC/browser-access stack (KasmVNC), and the in-container wiring that lets a workspace integrate with the Kasm platform (audio, clipboard, uploads/downloads, webcam, microphone, printing, profile sync, session recording, etc.).
The output is a family of images published as kasmweb/core-<distro>:<tag> (e.g. kasmweb/core-ubuntu-noble, kasmweb/core-fedora-41, kasmweb/core-alpine-321). Downstream repos — notably workspaces-images (single-app images like Chrome, Firefox, VS Code) and customer-built workspaces — inherit FROM these core images.
Related repos in the broader Kasm system:
kasm_backend(parent of this clone) — the backend services that orchestrate workspacesworkspaces-images— application-specific images built on top of these coresKasmVNC— the VNC server that renders the desktop to a browserkasm_squid_adapter,kasm_upload_service,kasm_printer_service,kasm-webcam-server,kasm-gamepad-server,profile-sync,kasm_smartcard_bridge,kasm_websocket_relay,kasm_audio_input_server,kasm_recorder_service,kasm-squid-builder— component binaries pulled in at image build time
Repository Layout
workspaces-core-images/
├── dockerfile-kasm-core # Debian/Ubuntu family (default)
├── dockerfile-kasm-core-alpine # Alpine
├── dockerfile-kasm-core-centos # CentOS 7
├── dockerfile-kasm-core-fedora # Fedora
├── dockerfile-kasm-core-kasmos # KasmOS (Debian bookworm slim)
├── dockerfile-kasm-core-oracle # Oracle Linux / RHEL / Rocky / Alma
├── dockerfile-kasm-core-suse # openSUSE
│
├── src/
│ ├── common/ # Shared across all distros
│ │ ├── install/ # kasm_vnc, profile_sync configs
│ │ ├── resources/images/ # Backgrounds, icons, branding
│ │ ├── scripts/kasm_hook_scripts # Session lifecycle hooks (see below)
│ │ └── startup_scripts/ # vnc_startup.sh and friends
│ │
│ ├── ubuntu/ # Ubuntu/Debian install scripts
│ │ ├── install/ # One subdir per feature (audio, webcam, …)
│ │ ├── xfce/ # XFCE desktop configs
│ │ └── icewm/ # IceWM (lightweight alt) configs
│ │
│ ├── alpine/ centos/ fedora*/ kasmos/
│ ├── opensuse/ oracle7/ oracle8/ oracle9/
│ ├── rhel9/ rockylinux*/ almalinux*/
│ ├── parrotos6/ kali/ # Distro-specific install scripts
│
├── ci-scripts/ # GitLab CI build/test/manifest tooling
│ ├── build.sh, test.sh # Called per matrix row from .gitlab-ci.yml
│ ├── template-gitlab.py # Jinja2 renderer for gitlab-ci.template
│ ├── template-vars.yaml # Matrix of images to build + change-file rules
│ ├── gitlab-ci.template # The actual CI config, rendered dynamically
│ ├── manifest.sh # Multi-arch manifest assembly
│ ├── scan/, vulnerability-filter.rego # Trivy scan + Rego filtering
│ └── readme.sh, quay_readme.sh # Dockerhub/Quay README publishing
│
├── bin/ # Prebuilt helpers shipped into images
│ ├── intel-gpu-dri3, intel-gpu-virtualgl, intel-gpu-zink
│
├── docs/core-<distro>/ # Per-image README/description for Dockerhub
│ # (README.md, description.txt, demo.txt)
│
└── kasm-desktop-kde/ # KDE desktop variant (WIP/placeholder)
What's inside a core image
Every dockerfile follows the same shape — a series of COPY + RUN bash $INST_SCRIPTS/<feature>/install_<feature>.sh blocks — and installs (roughly in order):
- Package rules — apt/yum/apk pinning and repo setup
- Base tools — curl, jq, sudo, xz, etc.
- Fonts — custom Kasm fonts + distro fonts
- Desktop environment — XFCE (default), IceWM (lightweight), or KDE variant
- KasmVNC — the browser-accessible VNC server (heart of the image)
- profile_sync — persists user profile to object storage between sessions
- kasm_upload_server — handles browser-initiated file uploads/downloads
- Audio output (
kasm_websocket_relay) — PulseAudio → WebSocket bridge for audio streaming - Audio input (
kasm_audio_input_server) — microphone passthrough from browser - Gamepad (
kasm_gamepad_server) — HID passthrough from browser - Webcam (
kasm_webcam_server) — virtual webcam device - Printer (
kasm_printer_service) — CUPS + start_cups.sh - Recorder (
kasm_recorder_service) — session recording via FFmpeg + KasmVNC capture - Cursors — custom cursor theme
- Squid (
kasm_squid_adapter+kasm-squid-builder) — per-session egress proxy - Smartcard (
kasm_smartcard_bridge) — CCID/pcscd passthrough - NVIDIA / VirtualGL — optional GPU acceleration
- Hook scripts —
src/common/scripts/kasm_hook_scripts/(see below) - Cleanup — strip caches, man pages, locales to shrink layers
Environment variables set by every core image:
HOME=/home/kasm-default-profile— template profile copied to/home/kasm-userat startupSTARTUPDIR=/dockerstartup— housesvnc_startup.shand the hook scriptsINST_SCRIPTS=/dockerstartup/install— scratch dir used only during build (removed after each install)KASM_VNC_PATH=/usr/share/kasmvnc
Runtime lifecycle
Containers start at vnc_startup.sh (in src/common/startup_scripts/). It:
- Logs to the Kasm API via
KASM_API_JWT/KASM_API_HOST/KASM_API_PORTif set - Regenerates the container user (
generate_container_user) so UIDs match the workspace config - Starts dbus, PulseAudio, KasmVNC, the desktop (XFCE by default, overridable via
START_XFCE4/START_ICEWM/ etc.), kasm_upload_server, the squid adapter, cups, profile_sync, the recorder - Invokes lifecycle hook scripts in
src/common/scripts/kasm_hook_scripts/at the right moments:kasm_post_run_root.sh— runs as root after core services are upkasm_post_run_user.sh— runs askasm-userafter loginkasm_pre_shutdown_root.sh/kasm_pre_shutdown_user.sh— graceful teardownkasm_end_session_recoverable.sh— for persistent/resumable sessions
- Tails the running processes; exits when the main desktop process dies
Downstream images can override hooks by placing replacements at the same path during their own build.
Build system
Local / manual build
The README shows the user-facing form:
sudo docker run --rm -it --shm-size=512m -p 6901:6901 \
-e VNC_PW=password --build-arg START_XFCE4=1 \
kasmweb/core-ubuntu-noble:<tag>
For building from source, each dockerfile takes these build args:
BASE_IMAGE— e.g.ubuntu:24.04,fedora:41,alpine:3.21DISTRO— selects whichsrc/<distro>/tree of install scripts to copyBG_IMG— which wallpaper fromsrc/common/resources/images/to useEXTRA_SH— optional extra install script (defaults tonoop.sh)
docker build \
-f dockerfile-kasm-core \
--build-arg BASE_IMAGE=ubuntu:24.04 \
--build-arg DISTRO=ubuntu \
--build-arg BG_IMG=bg_kasm.png \
-t kasmweb/core-ubuntu-noble:dev .
GitLab CI
The CI pipeline is dynamically generated:
template-gitlab.pyreadstemplate-vars.yaml(matrix of image names, base images, dockerfiles, change-file globs)- It renders
gitlab-ci.template(Jinja2) into a.gitlab-ci-child.yml - GitLab runs that child pipeline with stages: build → test → scan → manifest → release
Key scripts invoked from the template:
ci-scripts/build.sh NAME1 NAME2 BASE BG DISTRO DOCKERFILE— per-arch build, pushes to private image cacheci-scripts/test.sh ... ARCH AWS_ID AWS_KEY— spins up an EC2 instance to smoke-test the imageci-scripts/manifest.sh/weekly-manifest.sh— joins x86_64 + aarch64 into a single multi-arch manifestci-scripts/scan/+vulnerability-filter.rego— Trivy CVE scan with Rego-based allowlistci-scripts/readme.sh,quay_readme.sh— push the per-imagedocs/core-*/README.mdto Dockerhub/Quay
FILE_LIMITS gating: on feature branches the pipeline only builds images whose files: globs in template-vars.yaml actually changed. develop and release/* branches build everything. UNIVERSAL_CHANGE_FILES (at the top of template-vars.yaml) is the set of paths that force rebuilding every image — edit those conservatively.
Common tasks
Add support for a new distro version:
- Add a block to
template-vars.yaml(images:list) with its name, base image, dockerfile, and change-file globs - Create
src/<distro>/install/trees if a new family (usually just reuse ubuntu/fedora/alpine/etc.) - Add
docs/core-<distro>/withREADME.md,description.txt,demo.txt - Verify change-file globs pick up the right paths on feature-branch builds
Add a new in-container feature (e.g., new passthrough device):
- Create
src/ubuntu/install/<feature>/install_<feature>.sh(and peers for other distro families) - Add
COPY+RUN bash $INST_SCRIPTS/<feature>/install_<feature>.shto every relevant dockerfile - Wire the feature into
src/common/startup_scripts/vnc_startup.shif it needs a runtime process - Add the path to
UNIVERSAL_CHANGE_FILESintemplate-vars.yamlso all images rebuild on change
Debug a failing image at runtime:
- Start with
-e VNC_PW=passwordand connect tohttps://<host>:6901askasm_user/password - Check
/var/log/kasm-*and the container's stdout (vnc_startup.sh logs go there) - If running inside a real Kasm deployment, logs are also forwarded to the Kasm API via the
KASM_API_JWTpath in vnc_startup.sh
Conventions / gotchas
- Install scripts run at build time only.
$INST_SCRIPTSis removed after each install step — do not reference it from runtime code. - Downstream images inherit everything. Be conservative when adding packages; a 50MB addition to a core image fans out across every application image.
- Multi-arch. Every dockerfile must work on both
amd64andarm64. Shell out to$(arch)ordpkg --print-architecturerather than hardcoding. - No secrets in images. Anything in
$HOME/kasm-default-profileships in the public image. - Cleanup matters. Each feature's install script is expected to remove its own build-time caches before the layer closes (
apt clean,rm -rf /var/lib/apt/lists/*, etc.) — missing cleanup bloats every downstream image. dockerfile-kasm-coreis the reference. The other dockerfiles are variants with equivalent structure; when adding a feature, update them all in the same commit to avoid drift.
License
See LICENSE.md (Kasm Technologies license, not OSS).