feat(code-server): give the workspace its own volume

The workspace moves off the config volume onto code-server-workspace, so
wiping editor state and wiping code are separate acts.

The image only ever chowns the literal path /config/workspace, and reads
DEFAULT_WORKSPACE to pick the folder to open, so a named volume on /workspace
would come up root-owned and unwritable. Creating the directory in a local
Dockerfile seeds the volume with the right ownership instead.
This commit is contained in:
tiennm99 committed 2026-09-18 17:38:07 +07:00
1 parent ff9ec68b0b
commit a63c121075
4 files changed
+62 -5

No files matched your search

+21
View File
@@ -41,6 +41,27 @@ links out to each service. Per-service detail (variables, ports, storage)
belongs in that service's README, not the root one. Adding a service means
adding its README and a row to the root table.
## Workspace services
A service someone works *inside* — an editor, a coding agent, anything with a
shell — gets exactly two named volumes: one for the container user's home
directory, one mounted at `/workspace`. The home volume holds settings,
credentials and CLI logins; `/workspace` holds the code. Point whatever
variable selects the working directory at `/workspace`.
`code-server`, `paseo` and `opencode-web` all follow this.
A service with no human inside it does not: `openhands` keeps only its state
volume, because each agent session gets a container of its own.
The split is so that wiping one does not take the other. Reinstalling an editor
should not cost you a repository, and deleting a repository should not cost you
your extensions and logins.
Check who owns `/workspace` on a fresh volume. Docker creates it `root:root`
unless the image ships the directory, and an image that drops to a non-root
user will not be able to write there. `code-server` needs an explicit `chown`
for this reason; `paseo` and `opencode-web` do not.
## Environment variable order
`environment:` entries are ordered by how badly the service needs them — not
+4
View File
@@ -0,0 +1,4 @@
FROM lscr.io/linuxserver/code-server:latest
# Workspace directory, owned by the container user.
RUN mkdir -p /workspace && chown 1000:1000 /workspace
+33 -3
View File
@@ -57,6 +57,36 @@ Coolify or Dokploy. See the [root README](../README.md) for why.
## Storage
Everything lives in the `code-server-config` named volume mounted at `/config`;
the default workspace is `/config/workspace`. Removing the volume wipes your
files, settings, and extensions.
| Volume | Mount | Holds |
| --- | --- | --- |
| `code-server-config` | `/config` | Home directory: settings, extensions, shell history, CLI logins |
| `code-server-workspace` | `/workspace` | Code you work on |
Two volumes, the same split [paseo](../paseo/README.md) and
[opencode-web](../opencode-web/README.md) use: home in one, the workspace in
the other. Code survives a wipe of the editor's state, and the editor's state
survives a wipe of the code.
`DEFAULT_WORKSPACE` points at `/workspace` to match. It only chooses the folder
code-server opens; it does not move anything.
The `Dockerfile` exists only because of that move. The image hard-codes what it
hands to the `abc` user — `init-adduser` takes `/app`, `/config` and
`/defaults`, `init-code-server` takes `/config/workspace` by literal path — and
reads `DEFAULT_WORKSPACE` only to decide which folder to open. A named volume
on `/workspace` is therefore never chowned, comes up `root:root`, and the
editor cannot write a single file into it.
Creating the directory in the image fixes it without any runtime step: Docker
seeds an empty named volume from the image directory, ownership included, so
`/workspace` arrives owned by `abc`. It is the same reason `paseo` needs no
fixup — its upstream image ships `/workspace` already owned.
The alternative was a `chown` script in `/custom-cont-init.d`, the image's own
init hook. It was rejected because it needs a bind mount from the repository
into the container, and because the hook silently skips any script that has
lost its executable bit — a read-only workspace with nothing obvious to blame.
Baking `1000:1000` into the image costs the ability to change `PUID` at
runtime, which is free here: both services pin it to `1000`.
Anything outside these two volumes is lost on redeploy.
+4 -2
View File
@@ -1,6 +1,6 @@
services:
code-server:
image: 'lscr.io/linuxserver/code-server:latest'
build: .
hostname: ${SERVICE_HOSTNAME}
environment:
- PUID=1000
@@ -11,7 +11,7 @@ services:
- INSTALL_PACKAGES=gh|git|glab|unzip|zip
- NODEJS_MOD_VERSION=24
- TZ=Asia/Ho_Chi_Minh
- DEFAULT_WORKSPACE=/config/workspace
- DEFAULT_WORKSPACE=/workspace
- GIT_AUTHOR_NAME=${GIT_NAME}
- GIT_AUTHOR_EMAIL=${GIT_EMAIL}
- GIT_COMMITTER_NAME=${GIT_NAME}
@@ -20,6 +20,8 @@ services:
- PWA_APPNAME=code-server
volumes:
- 'code-server-config:/config'
- 'code-server-workspace:/workspace'
- '/var/run/docker.sock:/var/run/docker.sock:ro'
volumes:
code-server-config:
code-server-workspace: