feat(code-server): install Docker CLI and gh from vendor apt repos

- Install the Docker CLI with its Compose and Buildx plugins, and the GitHub
  CLI, from Docker's and GitHub's signed apt repositories; drop their home
  installs and the GitLab CLI from the README.
- Select the shell with SHELL and the start folder with DEFAULT_WORKSPACE
  instead of chsh and working_dir.
- Add an optional CODE_SERVER_APP_NAME.
- Document installing Java with SDKMAN.
This commit is contained in:
tiennm99 committed 2026-10-06 13:39:31 +07:00
1 parent 03a2016f23
commit c1ef9e7680
5 files changed
+84 -58

No files matched your search

+3
View File
@@ -13,3 +13,6 @@ GIT_EMAIL=
# Container hostname, also passed in as HOST -- the name zsh's prompt shows. # Container hostname, also passed in as HOST -- the name zsh's prompt shows.
# Not named HOSTNAME: the deploying shell's own HOSTNAME would override it. # Not named HOSTNAME: the deploying shell's own HOSTNAME would override it.
SERVICE_HOSTNAME=code-server SERVICE_HOSTNAME=code-server
# Name shown in the title bar and welcome page.
# CODE_SERVER_APP_NAME=code-server
+24 -3
View File
@@ -9,13 +9,34 @@ RUN apt-get update \
libffi-dev libssl-dev libyaml-dev zlib1g-dev \ libffi-dev libssl-dev libyaml-dev zlib1g-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# zsh as the container user's login shell. # Docker CLI with the Compose and Buildx plugins, from Docker's apt repository,
RUN chsh -s /usr/bin/zsh coder # and GitHub CLI, from GitHub's apt repository.
RUN install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg \
-o /etc/apt/keyrings/docker.asc \
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& chmod a+r /etc/apt/keyrings/docker.asc /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& printf '%s\n' \
'Types: deb' \
'URIs: https://download.docker.com/linux/debian' \
"Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")" \
'Components: stable' \
"Architectures: $(dpkg --print-architecture)" \
'Signed-By: /etc/apt/keyrings/docker.asc' \
> /etc/apt/sources.list.d/docker.sources \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
docker-ce-cli docker-buildx-plugin docker-compose-plugin gh \
&& rm -rf /var/lib/apt/lists/*
# Workspace directory, owned by the container user. # Workspace directory, owned by the container user.
RUN mkdir -p /workspace && chown 1000:1000 /workspace RUN mkdir -p /workspace && chown 1000:1000 /workspace
# Startup wrapper: joins the Docker socket's group, then runs the image's entrypoint. # Startup wrapper: enters DEFAULT_WORKSPACE, joins the Docker socket's group,
# then runs the image's entrypoint.
COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY entrypoint.sh /usr/local/bin/entrypoint.sh
USER 1000 USER 1000
+49 -54
View File
@@ -5,9 +5,10 @@ official `codercom/code-server` image.
The image ships code-server on Debian with `git`, `zsh`, `curl`, `sudo` and a The image ships code-server on Debian with `git`, `zsh`, `curl`, `sudo` and a
few editors. The `Dockerfile` adds `build-essential`, `bubblewrap`, `zip`, few editors. The `Dockerfile` adds `build-essential`, `bubblewrap`, `zip`,
`unzip` and the headers Ruby builds against, makes zsh the login shell, and `unzip`, the headers Ruby builds against, the Docker CLI with its Compose and
wraps the entrypoint so `coder` can use the Docker socket. Language toolchains Buildx plugins, and the GitHub CLI, and wraps the entrypoint so it starts in
and CLIs are installed into home, below. `/workspace` and `coder` can use the Docker socket. Language toolchains and
other CLIs are installed into home, below.
## Toolchains ## Toolchains
@@ -60,29 +61,20 @@ rbenv install "$V" && rbenv global "$V"
To get newer Ruby versions listed, `git -C "$(rbenv root)"/plugins/ruby-build pull`. To get newer Ruby versions listed, `git -C "$(rbenv root)"/plugins/ruby-build pull`.
Docker Compose and Buildx, as CLI plugins in `~/.docker/cli-plugins`, the Java, with SDKMAN, from its [install guide](https://sdkman.io/install/).
manual install from the SDKMAN and every JDK it installs live in `~/.sdkman`; the installer adds itself
[Compose docs](https://docs.docker.com/compose/install/linux/#install-the-plugin-manually) to `~/.bashrc` and `~/.zshrc`, and needs the `zip` and `unzip` the `Dockerfile`
and the [Buildx README](https://github.com/docker/buildx#manual-download). installs. `sdk install java` with no version takes SDKMAN's default, the
Nothing installs them together with the client: Docker's static archive holds current Temurin LTS:
only the client and daemon, and the packages that bundle all three are apt
packages, which land outside home. The client itself is below:
```sh ```sh
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} curl -s "https://get.sdkman.io" | bash
mkdir -p "$DOCKER_CONFIG/cli-plugins" . "$HOME/.sdkman/bin/sdkman-init.sh"
ARCH=$(dpkg --print-architecture) sdk install java
V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/docker/compose/releases/latest | sed 's|.*/||')
curl -fsSL "https://github.com/docker/compose/releases/download/$V/docker-compose-linux-$(uname -m)" -o "$DOCKER_CONFIG/cli-plugins/docker-compose"
V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/docker/buildx/releases/latest | sed 's|.*/||')
curl -fsSL "https://github.com/docker/buildx/releases/download/$V/buildx-$V.linux-$ARCH" -o "$DOCKER_CONFIG/cli-plugins/docker-buildx"
chmod +x "$DOCKER_CONFIG/cli-plugins/docker-compose" "$DOCKER_CONFIG/cli-plugins/docker-buildx"
``` ```
To upgrade them, run the same commands again. `sdk list java` shows other vendors and versions, and `sdk install gradle` or
`sdk install maven` adds a build tool the same way.
### Suggested by AI, may not be the optimal way ### Suggested by AI, may not be the optimal way
@@ -106,41 +98,21 @@ echo 'export PATH="$HOME/.local/go/bin:$HOME/go/bin:$PATH"' >> ~/.bashrc
To upgrade Go, `rm -rf ~/.local/go` and run the same commands again, without To upgrade Go, `rm -rf ~/.local/go` and run the same commands again, without
the `echo` line. the `echo` line.
Docker CLI, GitHub CLI, GitLab CLI and jq, as the release binaries each jq, the binary from its
project publishes, into `~/.local/bin`: [releases page](https://github.com/jqlang/jq/releases), into `~/.local/bin`,
with no documented location:
- Docker: [static binaries](https://docs.docker.com/engine/install/binaries/),
documented for `/usr/bin`. Only the client is taken; the daemon is the
host's, through the socket.
- GitHub CLI: the `.tar.gz` on [cli.github.com](https://cli.github.com/), with
no documented location.
- GitLab CLI: the binary from the
[releases page](https://gitlab.com/gitlab-org/cli/-/releases), with no
documented location.
- jq: the binary from the
[releases page](https://github.com/jqlang/jq/releases), with no documented
location.
```sh ```sh
mkdir -p ~/.local/bin mkdir -p ~/.local/bin
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
ARCH=$(dpkg --print-architecture) ARCH=$(dpkg --print-architecture)
V=$(curl -fsSL https://download.docker.com/linux/static/stable/$(uname -m)/ | grep -o 'docker-[0-9.]*\.tgz' | sort -V | tail -1)
curl -fsSL "https://download.docker.com/linux/static/stable/$(uname -m)/$V" | tar -C ~/.local/bin -xzf - --strip-components=1 docker/docker
V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/cli/cli/releases/latest | sed 's|.*/v||')
curl -fsSL "https://github.com/cli/cli/releases/download/v$V/gh_${V}_linux_$ARCH.tar.gz" | tar -C ~/.local/bin -xzf - --strip-components=2 "gh_${V}_linux_$ARCH/bin/gh"
V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://gitlab.com/gitlab-org/cli/-/releases/permalink/latest | sed 's|.*/v||')
curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v$V/downloads/glab_${V}_linux_$ARCH.tar.gz" | tar -C ~/.local/bin -xzf - --strip-components=1 bin/glab
V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/jqlang/jq/releases/latest | sed 's|.*/||') V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/jqlang/jq/releases/latest | sed 's|.*/||')
curl -fsSL "https://github.com/jqlang/jq/releases/download/$V/jq-linux-$ARCH" -o ~/.local/bin/jq && chmod +x ~/.local/bin/jq curl -fsSL "https://github.com/jqlang/jq/releases/download/$V/jq-linux-$ARCH" -o ~/.local/bin/jq && chmod +x ~/.local/bin/jq
``` ```
To upgrade one, run the `ARCH=` line and that tool's two lines again; the To upgrade it, run the same commands again, without the `echo` line; the new
new binary overwrites the old one. binary overwrites the old one.
## Environment ## Environment
@@ -149,6 +121,7 @@ new binary overwrites the old one.
| `PASSWORD` | Web UI login. Required. | | `PASSWORD` | Web UI login. Required. |
| `GIT_NAME` / `GIT_EMAIL` | Git author and committer identity | | `GIT_NAME` / `GIT_EMAIL` | Git author and committer identity |
| `SERVICE_HOSTNAME` | Container hostname, and the name the shell prompt shows (also passed as `HOST`) | | `SERVICE_HOSTNAME` | Container hostname, and the name the shell prompt shows (also passed as `HOST`) |
| `CODE_SERVER_APP_NAME` | Optional. Name in the title bar and welcome page; defaults to `code-server`. |
Generate a password with `openssl rand -base64 24`. Generate a password with `openssl rand -base64 24`.
@@ -168,8 +141,8 @@ its `\h` uses the real hostname.
## Docker access ## Docker access
The host's Docker socket is bind-mounted at `/var/run/docker.sock`. The image The host's Docker socket is bind-mounted at `/var/run/docker.sock`. The
ships no Docker CLI; install the client into home as above. Containers `Dockerfile` installs the client only; the daemon is the host's. Containers
started through it are siblings on the host, not children, so bind mounts in started through it are siblings on the host, not children, so bind mounts in
them resolve against host paths. them resolve against host paths.
@@ -207,8 +180,12 @@ Listens on `8080`; point the domain at it.
| `code-server-home` | `/home/coder` | Home directory: settings, extensions, shell history, CLI logins | | `code-server-home` | `/home/coder` | Home directory: settings, extensions, shell history, CLI logins |
| `code-server-workspace` | `/workspace` | Code you work on | | `code-server-workspace` | `/workspace` | Code you work on |
The image's entrypoint opens `.`, its working directory. `working_dir: The image's entrypoint opens `.`, its working directory. `entrypoint.sh`
/workspace` makes that the folder code-server opens. changes into `DEFAULT_WORKSPACE`, set to `/workspace` in `compose.yml`, before
starting it, so that is the folder code-server opens and where new terminals
start. Changing the variable moves both without editing the compose file's
structure. `docker exec` shells are not affected and start in the image's
`/home/coder`.
The `Dockerfile` also makes `/workspace` writable. The image does not The `Dockerfile` also makes `/workspace` writable. The image does not
ship that directory, so a named volume mounted there comes up `root:root`, ship that directory, so a named volume mounted there comes up `root:root`,
@@ -227,9 +204,27 @@ rbenv. `libffi-dev`, `libssl-dev`, `libyaml-dev` and `zlib1g-dev` are the
headers a rbenv-built Ruby needs for its `fiddle`, `openssl`, `psych` and headers a rbenv-built Ruby needs for its `fiddle`, `openssl`, `psych` and
`zlib` extensions; without them `rbenv install` fails or leaves those out. `zlib` extensions; without them `rbenv install` fails or leaves those out.
The image leaves `coder` with `/bin/bash` as its login shell. The editor's The Docker CLI, its Compose and Buildx plugins, and the GitHub CLI come from
terminal opens zsh regardless, but tools that read the login shell from Docker's and GitHub's signed apt repositories, which is each vendor's documented
`/etc/passwd` or `$SHELL` get bash, so the `Dockerfile` sets it to zsh. install for Debian. Neither documents an install into home, and Coolify builds
with `--pull`, so they update with each rebuild rather than by hand. The GitLab
CLI is not in the image: GitLab publishes no apt repository, only Homebrew and
a community one.
Copies of `docker`, `gh` or the plugins left in `~/.local/bin` or
`~/.docker/cli-plugins` from an older setup take precedence over the image's
and should be deleted.
## Shell
`SHELL=/bin/zsh` in `compose.yml` picks the shell. The editor's terminal takes
its default from `$SHELL` first and only falls back to the login shell in
`/etc/passwd`, so the variable is enough, and switching to another shell the
image ships means changing one line rather than rebuilding. `sudo -E` in
`entrypoint.sh` keeps the variable; without it sudo would replace it with
root's `/bin/bash`. The value is written literally, not read from `.env`,
because every deploying shell exports its own `SHELL`, which interpolation
would pick up first.
## Image ## Image
+3 -1
View File
@@ -3,15 +3,17 @@ services:
build: . build: .
restart: unless-stopped restart: unless-stopped
hostname: ${SERVICE_HOSTNAME} hostname: ${SERVICE_HOSTNAME}
working_dir: /workspace
environment: environment:
- PASSWORD=${PASSWORD:?required} - PASSWORD=${PASSWORD:?required}
- TZ=Asia/Ho_Chi_Minh - TZ=Asia/Ho_Chi_Minh
- DEFAULT_WORKSPACE=/workspace
- SHELL=/bin/zsh
- GIT_AUTHOR_NAME=${GIT_NAME} - GIT_AUTHOR_NAME=${GIT_NAME}
- GIT_AUTHOR_EMAIL=${GIT_EMAIL} - GIT_AUTHOR_EMAIL=${GIT_EMAIL}
- GIT_COMMITTER_NAME=${GIT_NAME} - GIT_COMMITTER_NAME=${GIT_NAME}
- GIT_COMMITTER_EMAIL=${GIT_EMAIL} - GIT_COMMITTER_EMAIL=${GIT_EMAIL}
- HOST=${SERVICE_HOSTNAME} - HOST=${SERVICE_HOSTNAME}
# - CODE_SERVER_APP_NAME=${CODE_SERVER_APP_NAME:-code-server}
volumes: volumes:
- 'code-server-home:/home/coder' - 'code-server-home:/home/coder'
- 'code-server-workspace:/workspace' - 'code-server-workspace:/workspace'
+5
View File
@@ -1,6 +1,11 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
# Start in DEFAULT_WORKSPACE, the folder code-server opens.
if [ -n "${DEFAULT_WORKSPACE:-}" ]; then
cd "$DEFAULT_WORKSPACE"
fi
# Add coder to the group that owns the mounted Docker socket, then restart # Add coder to the group that owns the mounted Docker socket, then restart
# under that group. # under that group.
if [ -S /var/run/docker.sock ]; then if [ -S /var/run/docker.sock ]; then