From e4a8e4b7f0e8eae2dc0047e68a65d2063477e643 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 19 Sep 2024 18:42:53 +0100 Subject: [PATCH] chore: update dind examples to use onCreateCommand (#350) (cherry picked from commit 7c8e6a4da574e87af2a67b189120cac6bb5c1ceb) --- docs/docker.md | 25 ++++++++++++------- examples/docker/02_dind/Dockerfile | 25 ++++++++++++++++--- examples/docker/02_dind/devcontainer.json | 5 ++-- examples/docker/02_dind/entrypoint.sh | 7 ------ examples/docker/02_dind/on-create.sh | 22 ++++++++++++++++ examples/docker/03_dind_feature/Dockerfile | 23 +++++++++++++++-- .../docker/03_dind_feature/devcontainer.json | 3 ++- examples/docker/03_dind_feature/entrypoint.sh | 7 ------ examples/docker/03_dind_feature/on-create.sh | 18 +++++++++++++ examples/docker/04_dind_rootless/Dockerfile | 11 +++++--- .../docker/04_dind_rootless/devcontainer.json | 5 ++-- .../{entrypoint.sh => on-create.sh} | 4 +-- 12 files changed, 115 insertions(+), 40 deletions(-) delete mode 100755 examples/docker/02_dind/entrypoint.sh create mode 100755 examples/docker/02_dind/on-create.sh delete mode 100755 examples/docker/03_dind_feature/entrypoint.sh create mode 100755 examples/docker/03_dind_feature/on-create.sh rename examples/docker/04_dind_rootless/{entrypoint.sh => on-create.sh} (79%) diff --git a/docs/docker.md b/docs/docker.md index ca09c72..56ce9d0 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -9,7 +9,8 @@ from inside Envbuilder. > you may need to instead add the relevant content of the init script to your > agent startup script in your template. > For example: -> ``` +> +> ```terraform > resource "coder_agent" "dev" { > ... > startup_script = <<-EOT @@ -43,7 +44,6 @@ docker run -it --rm \ ghcr.io/coder/envbuilder:latest ``` - ## Docker-in-Docker (DinD) **Security:** Low @@ -57,8 +57,8 @@ Example: > Note that due to a lack of init system, the Docker daemon > needs to be started separately inside the container. In this example, we -> create a custom entrypoint to start the Docker daemon in the background and -> call this entrypoint via `ENVBUILDER_INIT_SCRIPT`. +> create a custom script to start the Docker daemon in the background and +> call this entrypoint via the Devcontainer `onCreateCommand` lifecycle hook. ```console docker run -it --rm \ @@ -66,7 +66,7 @@ docker run -it --rm \ -v /tmp/envbuilder:/workspaces \ -e ENVBUILDER_GIT_URL=https://github.com/coder/envbuilder \ -e ENVBUILDER_DEVCONTAINER_DIR=/workspaces/envbuilder/examples/docker/02_dind \ - -e ENVBUILDER_INIT_SCRIPT=/entrypoint.sh \ + -e ENVBUILDER_INIT_SCRIPT=bash \ ghcr.io/coder/envbuilder:latest ``` @@ -75,8 +75,14 @@ docker run -it --rm \ The above can also be accomplished using the [`docker-in-docker` Devcontainer feature](https://github.com/devcontainers/features/tree/main/src/docker-in-docker). -> Note: we still need the custom entrypoint to start the docker startup script. -> See https://github.com/devcontainers/features/blob/main/src/docker-in-docker/devcontainer-feature.json#L60 +> Note: we still need the `onCreateCommand` to start Docker. +> See +> [here](https://github.com/devcontainers/features/blob/main/src/docker-in-docker/devcontainer-feature.json#L65) +> for more details. +> +> Known issue: `/run` does not get symlinked correctly to `/var/run`. +> To work around this, we create the symlink manually before running +> the script to start the Docker daemon. Example: @@ -86,7 +92,7 @@ docker run -it --rm \ -v /tmp/envbuilder:/workspaces \ -e ENVBUILDER_GIT_URL=https://github.com/coder/envbuilder \ -e ENVBUILDER_DEVCONTAINER_DIR=/workspaces/envbuilder/examples/docker/03_dind_feature \ - -e ENVBUILDER_INIT_SCRIPT=/entrypoint.sh \ + -e ENVBUILDER_INIT_SCRIPT=bash \ ghcr.io/coder/envbuilder:latest ``` @@ -95,7 +101,7 @@ docker run -it --rm \ **Security:** Medium **Convenience:** Medium -This approach runs a Docker daemon in *rootless* mode. +This approach runs a Docker daemon in _rootless_ mode. While this still requires a privileged container, this allows you to restrict usage of the `root` user inside the container, as the Docker daemon will be run under a "fake" root user (via `rootlesskit`). The user inside the workspace can @@ -129,6 +135,7 @@ including transparently enabling Docker inside workspaces. Most notably, it access inside their workspaces, if required. Example: + ```console docker run -it --rm \ -v /tmp/envbuilder:/workspaces \ diff --git a/examples/docker/02_dind/Dockerfile b/examples/docker/02_dind/Dockerfile index 70a215b..aa29519 100644 --- a/examples/docker/02_dind/Dockerfile +++ b/examples/docker/02_dind/Dockerfile @@ -1,6 +1,23 @@ FROM ubuntu:noble + +# Install Docker using Docker's convenience script. RUN apt-get update && \ - apt-get install -y curl apt-transport-https && \ - curl -fsSL https://get.docker.com/ | sh -s - -ADD entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file + apt-get install -y curl sudo apt-transport-https && \ + curl -fsSL https://get.docker.com/ | sh -s - + +# The ubuntu:noble image includes a non-root user by default, +# but it does not have sudo privileges. We need to set this up. +# Note: we chown /var/run/docker.sock to the non-root user +# in the onCreateCommand script. Ideally you would add the +# non-root user to the docker group, but in this scenario +# this is a 'single-user' environment. It also avoids us +# having to run `newgrp docker`. +RUN echo "ubuntu ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ubuntu + +# Add our onCreateCommand script. +ADD on-create.sh /on-create.sh + +# Switch to the non-root user. +USER ubuntu + +ENTRYPOINT ["bash"] diff --git a/examples/docker/02_dind/devcontainer.json b/examples/docker/02_dind/devcontainer.json index 1933fd8..6649501 100644 --- a/examples/docker/02_dind/devcontainer.json +++ b/examples/docker/02_dind/devcontainer.json @@ -1,5 +1,6 @@ { "build": { "dockerfile": "Dockerfile" - } -} \ No newline at end of file + }, + "onCreateCommand": "/on-create.sh" +} diff --git a/examples/docker/02_dind/entrypoint.sh b/examples/docker/02_dind/entrypoint.sh deleted file mode 100755 index 38ac331..0000000 --- a/examples/docker/02_dind/entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -nohup dockerd > /var/log/docker.log 2>&1 & - -exec bash --login \ No newline at end of file diff --git a/examples/docker/02_dind/on-create.sh b/examples/docker/02_dind/on-create.sh new file mode 100755 index 0000000..8b369e2 --- /dev/null +++ b/examples/docker/02_dind/on-create.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Start Docker in the background. +sudo -u root /bin/sh -c 'nohup dockerd > /var/log/docker.log &' + +# Wait up to 10 seconds for Docker to start. +for attempt in $(seq 1 10); do + if [[ $attempt -eq 10 ]]; then + echo "Failed to start Docker" + exit 1 + fi + if [[ ! -e /var/run/docker.sock ]]; then + sleep 1 + else + break + fi +done + +# Change the owner of the Docker socket so that the non-root user can use it. +sudo chown ubuntu:docker /var/run/docker.sock diff --git a/examples/docker/03_dind_feature/Dockerfile b/examples/docker/03_dind_feature/Dockerfile index 12f1c1a..49c6646 100644 --- a/examples/docker/03_dind_feature/Dockerfile +++ b/examples/docker/03_dind_feature/Dockerfile @@ -1,3 +1,22 @@ FROM ubuntu:noble -ADD entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file + +# Install some dependencies such as curl and sudo. +# Also set up passwordless sudo for the ubuntu user. +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + curl \ + sudo \ + apt-transport-https && \ + echo "ubuntu ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ubuntu + +# Add our onCreateCommand script. +ADD on-create.sh /on-create.sh + +# Switch to the non-root user. +USER ubuntu + +# The devcontainer feature provides /usr/local/share/docker-init.sh +# which will handle most of the steps of setting up Docker. +# We can't put this in the entrypoint as it gets overridden, so +# we call it in the on-create script. +ENTRYPOINT ["bash"] diff --git a/examples/docker/03_dind_feature/devcontainer.json b/examples/docker/03_dind_feature/devcontainer.json index e1b5a18..58616a6 100644 --- a/examples/docker/03_dind_feature/devcontainer.json +++ b/examples/docker/03_dind_feature/devcontainer.json @@ -2,7 +2,8 @@ "build": { "dockerfile": "Dockerfile" }, + "onCreateCommand": "/on-create.sh", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {} } -} \ No newline at end of file +} diff --git a/examples/docker/03_dind_feature/entrypoint.sh b/examples/docker/03_dind_feature/entrypoint.sh deleted file mode 100755 index d18fb7d..0000000 --- a/examples/docker/03_dind_feature/entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -/usr/local/share/docker-init.sh - -exec bash --login \ No newline at end of file diff --git a/examples/docker/03_dind_feature/on-create.sh b/examples/docker/03_dind_feature/on-create.sh new file mode 100755 index 0000000..96bef1c --- /dev/null +++ b/examples/docker/03_dind_feature/on-create.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Known issue: Kaniko does not symlink /run => /var/run properly. +# This results in /var/run/ being owned by root:root which interferes +# with accessing the Docker socket even if the permissions are set +# correctly. Workaround: symlink it manually +sudo ln -s /run /var/run + +# Run the docker init script. This needs to be +# run as root. It will take care of starting the +# daemon and adding the ubuntu user to the docker +# group. +sudo /usr/local/share/docker-init.sh + +# Change the owner of the Docker socket so that the non-root user can use it. +sudo chown ubuntu:docker /var/run/docker.sock diff --git a/examples/docker/04_dind_rootless/Dockerfile b/examples/docker/04_dind_rootless/Dockerfile index 5358ce6..2d88aa1 100644 --- a/examples/docker/04_dind_rootless/Dockerfile +++ b/examples/docker/04_dind_rootless/Dockerfile @@ -1,8 +1,11 @@ FROM ubuntu:noble + # Based on UID of ubuntu user in container. ENV XDG_RUNTIME_DIR /run/user/1000 ENV DOCKER_HOST unix:///${XDG_RUNTIME_DIR}/docker.sock + # Setup as root +USER root RUN apt-get update && \ # Install prerequisites apt-get install -y apt-transport-https curl iproute2 uidmap && \ @@ -19,6 +22,8 @@ USER ubuntu RUN dockerd-rootless-setuptool.sh install && \ docker context use rootless && \ mkdir -p /home/ubuntu/.local/share/docker -# Add our custom entrypoint -ADD entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file + +# Add our onCreateCommand script. +ADD on-create.sh /on-create.sh + +ENTRYPOINT ["bash"] \ No newline at end of file diff --git a/examples/docker/04_dind_rootless/devcontainer.json b/examples/docker/04_dind_rootless/devcontainer.json index 1933fd8..6649501 100644 --- a/examples/docker/04_dind_rootless/devcontainer.json +++ b/examples/docker/04_dind_rootless/devcontainer.json @@ -1,5 +1,6 @@ { "build": { "dockerfile": "Dockerfile" - } -} \ No newline at end of file + }, + "onCreateCommand": "/on-create.sh" +} diff --git a/examples/docker/04_dind_rootless/entrypoint.sh b/examples/docker/04_dind_rootless/on-create.sh similarity index 79% rename from examples/docker/04_dind_rootless/entrypoint.sh rename to examples/docker/04_dind_rootless/on-create.sh index 6c8a626..ba2fced 100755 --- a/examples/docker/04_dind_rootless/entrypoint.sh +++ b/examples/docker/04_dind_rootless/on-create.sh @@ -3,6 +3,4 @@ set -euo pipefail # Start the rootless docker daemon as a non-root user -nohup rootlesskit --net=slirp4netns --mtu=1500 --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run dockerd > "/tmp/dockerd-rootless.log" 2>&1 & - -exec bash --login \ No newline at end of file +nohup rootlesskit --net=slirp4netns --mtu=1500 --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run dockerd >"/tmp/dockerd-rootless.log" 2>&1 &