Containerized LM Studio, in two flavors, built from a single unified Dockerfile:
lmstudio— the full desktop app, streamed to your browser via Selkies, on top of the linuxserver.io Selkies base image.llmster— the headless daemon (LM Studio'slmsCLI), serving the OpenAI-compatible API with no GUI. Built incpuandvulkan(GPU offload) variants.
The lmstudio (webui) image also runs llmster internally as a second background service — once a model backend is present, it automatically starts serving the API alongside the desktop app.
.
├── LMStudio/
│ ├── Dockerfile # unified, multi-stage: webui / cpu / vulkan targets
│ ├── Headless/
│ │ └── start.sh # llmster (cpu/vulkan) entrypoint
│ └── WebUI/
│ └── services/s6-overlay/ # s6 service definitions for process supervision
│ ├── lmstudio/ # desktop app service
│ │ ├── run # s6 service: launches the LM Studio desktop app as `abc`
│ │ └── dependencies.d/selkies
│ └── llmster/ # headless API service
│ ├── run # s6 service: waits for `lms`, then starts the API server too
│ └── dependencies.d/lmstudio
├── bake.sh # wrapper: injects git SHA/ref and runs buildx bake
├── docker-bake.hcl # build definitions: multi-target, OCI labels, base pinning
├── docker-compose.yml # runtime: ports, volumes, GPU passthrough anchors
├── .github/workflows/
│ └── docker-build-push.yml # CI: multi-registry build and push workflow
└── data/ # gitignored — bind-mounted model storage
Images are built with docker buildx bake, not docker compose build:
./bake.sh # builds all three images (default group)
./bake.sh --load # ...and loads into local Docker engine
./bake.sh lmstudio # just the desktop/WebUI image
./bake.sh llmster # just the headless CPU image
./bake.sh llmster-vulkan # headless image with Vulkan libs for GPU offload
./bake.sh --push # build and push to registry
./bake.sh --print # resolve and print build config without buildingThe bake.sh wrapper automatically injects:
GIT_SHA: current commit hash (for OCI image revision label)GIT_REF: current branch or tag name (for OCI ref.name label)
See docker-bake.hcl for the complete variable list:
| Variable | Default | Purpose |
|---|---|---|
REGISTRY |
ghcr.io/arasemco |
Image registry/namespace prefix |
SOURCE_URL |
https://github.com/arasemco/lmstudio |
Source code URL (for OCI labels) |
LMSTUDIO_VERSION |
0.4.23-1 |
LM Studio desktop AppImage version |
LLMSTER_VERSION |
0.0.23-1 |
llmster/lms CLI version |
PLATFORMS |
linux/amd64 |
Comma-separated platforms (e.g., linux/amd64,linux/arm64) |
To override:
REGISTRY=git.example.com/org PLATFORMS=linux/amd64,linux/arm64 ./bake.sh --pushBase images are pinned by digest for reproducible builds:
- Debian:
docker.io/library/debian:bookworm-slim@sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171 - Selkies:
ghcr.io/linuxserver/baseimage-selkies:ubuntunoble@sha256:7cab02f9222937ad1e704cd904534d69a2300b2406af466241817be0ce1d0493
Update the digest in docker-bake.hcl if you need to refresh the base.
docker-compose.yml only runs the images bake already built — it has no build: section:
docker compose up lmstudio # desktop app — browser UI on :3000, API on :1234
docker compose up llmster # headless daemon — API on :1234 only (uses :vulkan tag by default)- lmstudio: open
http://<host>:3000for the streamed desktop. - llmster: OpenAI-compatible API at
http://<host>:1234/v1.
Both services mount ./data/models to /config/.lmstudio/models for persistent model storage.
Everything runs as user abc, UID/GID 911, home /config — the linuxserver.io base image's own built-in default. The headless (llmster) targets are built from scratch to match this identity exactly, rather than using a different user and reconciling the two later.
The s6 init system handles PUID/PGID remapping at container start if you set these environment variables:
environment:
- PUID=1000
- PGID=1000Add these to the docker-compose.yml service definition if you need to match host permissions.
docker-compose.yml defines two reusable YAML anchors, commented out by default on each service:
# Nvidia (requires NVIDIA Container Toolkit):
#<<: *gpu-nvidia
# AMD/Intel (passthrough /dev/kfd + /dev/dri):
#<<: *gpu-amdUncomment whichever applies:
- Nvidia: uses
deploy.resources.reservations.devices(requires the NVIDIA Container Toolkit on the host) - AMD/Intel: uses
/dev/kfd+/dev/drirender-node passthrough with thevideogroup and unconfined seccomp
For GPU offload on the headless image, use the llmster-vulkan target. The compose file already points llmster at the :vulkan tag by default.
The GitHub Actions workflow (.github/workflows/docker-build-push.yml) automatically:
- Builds all three targets on pushes to
masterand tags - Pushes to configured registries (GHCR and/or self-hosted)
- Cancels in-progress runs on the same branch/ref
- Runs on pull requests (build-only, no push)
- Injects
GIT_SHA,GIT_REF,SOURCE_URL, andREGISTRYvariables
The workflow supports both GitHub-hosted and self-hosted Gitea instances without hardcoded assumptions.
- Docker with the
buildxplugin (v0.10+) - ~4 GB+ free disk for the desktop image, less for headless-only
- A registry (GHCR, Docker Hub, or self-hosted) if pushing images
- NVIDIA Container Toolkit (optional, for Nvidia GPU passthrough)