mirror of
https://github.com/tiennm99/composes.git
synced 2026-10-11 03:13:16 +00:00
feat(code-server): join the Docker socket's group at startup and add build tools
Add an entrypoint wrapper that reads the socket's GID, adds coder to a matching group and re-execs the image's entrypoint under it, so docker works without sudo on any host. Install build-essential and the headers rbenv needs to build Ruby, and make zsh the login shell. Document home installs for rbenv, Docker Compose and Buildx plugins, and jq.
This commit is contained in:
1 parent
56b1c5f931
commit
4c37d45377
3 files changed
+115
-12
No files matched your search
+10
-1
@@ -4,10 +4,19 @@ USER root
|
||||
|
||||
# Extra system packages.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends bubblewrap unzip zip \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential bubblewrap unzip zip \
|
||||
libffi-dev libssl-dev libyaml-dev zlib1g-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# zsh as the container user's login shell.
|
||||
RUN chsh -s /usr/bin/zsh coder
|
||||
|
||||
# Workspace directory, owned by the container user.
|
||||
RUN mkdir -p /workspace && chown 1000:1000 /workspace
|
||||
|
||||
# Startup wrapper: joins the Docker socket's group, then runs the image's entrypoint.
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
USER 1000
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh", "--bind-addr", "0.0.0.0:8080", "."]
|
||||
+84
-11
@@ -4,8 +4,10 @@
|
||||
official `codercom/code-server` image.
|
||||
|
||||
The image ships code-server on Debian with `git`, `zsh`, `curl`, `sudo` and a
|
||||
few editors. The `Dockerfile` adds `bubblewrap`, `zip` and `unzip`. Language
|
||||
toolchains and CLIs are installed into home, below.
|
||||
few editors. The `Dockerfile` adds `build-essential`, `bubblewrap`, `zip`,
|
||||
`unzip` and the headers Ruby builds against, makes zsh the login shell, and
|
||||
wraps the entrypoint so `coder` can use the Docker socket. Language toolchains
|
||||
and CLIs are installed into home, below.
|
||||
|
||||
## Toolchains
|
||||
|
||||
@@ -40,6 +42,48 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
uv python install 3.13
|
||||
```
|
||||
|
||||
Ruby, with rbenv and its ruby-build plugin, each cloned with git as their
|
||||
READMEs document ([rbenv](https://github.com/rbenv/rbenv#basic-git-checkout),
|
||||
[ruby-build](https://github.com/rbenv/ruby-build#clone-as-rbenv-plugin-using-git)).
|
||||
Debian's `ruby` package is 3.3, several releases behind. Rubies are compiled into
|
||||
`~/.rbenv/versions` against the headers the `Dockerfile` installs.
|
||||
`rbenv init` adds itself to the login shell's startup file, `~/.zprofile`:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
|
||||
~/.rbenv/bin/rbenv init
|
||||
eval "$(~/.rbenv/bin/rbenv init - zsh)"
|
||||
git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
|
||||
V=$(rbenv install -l 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
|
||||
rbenv install "$V" && rbenv global "$V"
|
||||
```
|
||||
|
||||
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
|
||||
manual install from the
|
||||
[Compose docs](https://docs.docker.com/compose/install/linux/#install-the-plugin-manually)
|
||||
and the [Buildx README](https://github.com/docker/buildx#manual-download).
|
||||
Nothing installs them together with the client: Docker's static archive holds
|
||||
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
|
||||
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||||
mkdir -p "$DOCKER_CONFIG/cli-plugins"
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
|
||||
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.
|
||||
|
||||
### Suggested by AI, may not be the optimal way
|
||||
|
||||
These use each project's official download, but no official doc covers
|
||||
@@ -62,8 +106,8 @@ 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
|
||||
the `echo` line.
|
||||
|
||||
Docker CLI, GitHub CLI and GitLab CLI, as the release binaries each project
|
||||
publishes, into `~/.local/bin`:
|
||||
Docker CLI, GitHub CLI, GitLab CLI and jq, as the release binaries each
|
||||
project publishes, into `~/.local/bin`:
|
||||
|
||||
- Docker: [static binaries](https://docs.docker.com/engine/install/binaries/),
|
||||
documented for `/usr/bin`. Only the client is taken; the daemon is the
|
||||
@@ -73,6 +117,9 @@ publishes, into `~/.local/bin`:
|
||||
- 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
|
||||
mkdir -p ~/.local/bin
|
||||
@@ -87,6 +134,9 @@ curl -fsSL "https://github.com/cli/cli/releases/download/v$V/gh_${V}_linux_$ARCH
|
||||
|
||||
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|.*/||')
|
||||
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
|
||||
@@ -123,10 +173,24 @@ ships no Docker CLI; install the client into home as above. Containers
|
||||
started through it are siblings on the host, not children, so bind mounts in
|
||||
them resolve against host paths.
|
||||
|
||||
The socket belongs to the host's `docker` group, which `coder` is not in.
|
||||
Run it as `sudo ~/.local/bin/docker`: sudo needs no password in this image,
|
||||
but it resets `PATH`, hence the full path. Access to the socket is root on the
|
||||
host, which is accepted here because this is a single-user dev box.
|
||||
The socket belongs to the host's `docker` group, whose GID differs from host
|
||||
to host and is not exported anywhere a compose file could read it, so a fixed
|
||||
`group_add:` would break on another host. Instead `entrypoint.sh` runs first
|
||||
at startup: it reads the GID off the socket, adds `coder` to a group with that
|
||||
GID (creating `docker-host` if none exists) and restarts the image's own
|
||||
entrypoint under the new group. `docker` then works for `coder` without sudo,
|
||||
in the editor's terminal and in `docker exec` shells alike. Access to the
|
||||
socket is root on the host, which is accepted here because this is a
|
||||
single-user dev box.
|
||||
|
||||
The wrapper keeps the image's `USER 1000`, so `docker exec` still opens a
|
||||
shell as `coder`, and gets root through the image's passwordless sudo. A
|
||||
running process cannot join a group, so it re-execs through `sudo -E setpriv
|
||||
--init-groups`, which loads the new group; `PATH` and `USER` are passed
|
||||
explicitly because sudo resets both. sudo stays as PID 1 and forwards stop
|
||||
signals to code-server. On a container restart `coder` is already in the
|
||||
group, and the wrapper hands straight to the image's entrypoint. Without a
|
||||
socket mounted it does nothing.
|
||||
|
||||
The `:ro` flag is not a security boundary. It marks the socket file read-only,
|
||||
but clients reach the Docker API by connecting to the socket, which a
|
||||
@@ -154,9 +218,18 @@ seeds an empty named volume from the image's directory, ownership included.
|
||||
The home volume needs no such step, because the image already ships
|
||||
`/home/coder` owned by `coder`.
|
||||
|
||||
`bubblewrap`, `zip` and `unzip` are in the `Dockerfile` rather than home
|
||||
because their projects publish no standalone binaries; Debian's packages are
|
||||
the install method, and they land outside home.
|
||||
`build-essential`, `bubblewrap`, `zip` and `unzip` are in the `Dockerfile`
|
||||
rather than home because their projects publish no standalone binaries;
|
||||
Debian's packages are the install method, and they land outside home.
|
||||
`build-essential` supplies the `gcc` and `make` that native builds expect:
|
||||
node-gyp addons, Python sdists, Rust crates using `cc`, and Ruby built by
|
||||
rbenv. `libffi-dev`, `libssl-dev`, `libyaml-dev` and `zlib1g-dev` are the
|
||||
headers a rbenv-built Ruby needs for its `fiddle`, `openssl`, `psych` and
|
||||
`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
|
||||
terminal opens zsh regardless, but tools that read the login shell from
|
||||
`/etc/passwd` or `$SHELL` get bash, so the `Dockerfile` sets it to zsh.
|
||||
|
||||
## Image
|
||||
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Add coder to the group that owns the mounted Docker socket, then restart
|
||||
# under that group.
|
||||
if [ -S /var/run/docker.sock ]; then
|
||||
gid=$(stat -c %g /var/run/docker.sock)
|
||||
if ! id -G | tr ' ' '\n' | grep -qx "$gid"; then
|
||||
group=$(getent group "$gid" | cut -d: -f1)
|
||||
if [ -z "$group" ]; then
|
||||
group=docker-host
|
||||
sudo groupadd -g "$gid" "$group"
|
||||
fi
|
||||
sudo usermod -aG "$group" coder
|
||||
exec sudo -E setpriv --reuid=coder --regid=coder --init-groups \
|
||||
env PATH="$PATH" USER=coder /usr/bin/entrypoint.sh "$@"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start the image's own entrypoint.
|
||||
exec /usr/bin/entrypoint.sh "$@"
|
||||
Reference in new issue
Block a user