Skip to content
This repository has been archived by the owner on Nov 30, 2023. It is now read-only.

Latest commit

 

History

History

docker-from-docker

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Docker from Docker

Summary

Access your host's Docker install from inside a dev container. Installs Docker extension in the container along with needed CLIs.

Metadata Value
Contributors The VS Code team
Categories Other
Definition type Dockerfile
Works in Codespaces Yes
Container host OS support Linux, macOS, Windows
Container OS Debian (though Ubuntu could be used instead)
Languages, platforms Any

Note: There is also a Docker Compose variation of this same definition. If you need to mount folders within the dev container into your own containers, you may find Docker in Docker meets your needs better, though with a potential performance penalty.

Description

Dev containers can be useful for all types of applications including those that also deploy into a container based-environment. While you can directly build and run the application inside the dev container you create, you may also want to test it by deploying a built container image into your local Docker Desktop instance without affecting your dev container.

This example illustrates how you can do this by running CLI commands and using the Docker VS Code extension right from inside your dev container. It installs the Docker extension inside the container so you can use its full feature set with your project.

Note: If preferred, you can use the related docker/moby install script in your own existing Dockerfiles instead.

The included .devcontainer/Dockerfile can be altered to work with other Debian/Ubuntu-based container images such as node or python. You'll also need to update remoteUser in .devcontainer/devcontainer.json in cases where a vscode user does not exist in the image you select. For example, to use mcr.microsoft.com/vscode/devcontainers/javascript-node, update the Dockerfile as follows:

FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:14

...and since the user in this container is node, update devcontainer.json as follows:

"remoteUser": "node"

How it works

While recommend just using docker/moby script from the script library as an easy way to get this running in your own existing container, this section will outline the how you can selectively add this functionality to your own Dockerfile in two parts: enabling access to Docker for the root user, and enabling it for a non-root user.

Enabling root user access to Docker in the container

You can adapt your own existing development container Dockerfile to support this scenario when running as root by following these steps:

  1. First, install the Docker CLI in your container. From .devcontainer/Dockerfile:

    # Install Docker CE CLI
    RUN apt-get update \
        && apt-get install -y apt-transport-https ca-certificates curl gnupg2 lsb-release \
        && curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \
        && echo "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list \
        && apt-get update \
        && apt-get install -y docker-ce-cli
    
    # Install Docker Compose
    RUN LATEST_COMPOSE_VERSION=$(curl -sSL "https://api.github.com/repos/docker/compose/releases/latest" | grep -o -P '(?<="tag_name": ").+(?=")') \
        && curl -sSL "https://github.com/docker/compose/releases/download/${LATEST_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose \
        && chmod +x /usr/local/bin/docker-compose
  2. Then just forward the Docker socket by mounting it in the container using the mounts property. From .devcontainer/devcontainer.json:

    "runArgs": ["--init"],
    "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ]

    While technically optional, --init enables an init process to properly handle signals and ensure Zombie Processes are cleaned up.

  3. Press F1 and run Remote-Containers: Rebuild Container so the changes take effect.

Enabling non-root access to Docker in the container

This can be a bit trickier than it might first seem if you're looking to ensure things run locally on macOS, Windows, and Linux as well as in Codespaces. The docker script used in this container automatically detects the right thing to do to enable this scenario, but it uses the following two approaches to accomplish it.

In short, you can ignore this if you use the script, but here's what it does.

Adding the user to a Docker group

In some environments like Codespaces, this is relatively simple to achieve if the Docker socket already has a group other than root on it. To see if this is the case, open a terminal in VS Code when connected to the container to check:

stat -c '%g' /var/run/docker.sock

If you get a number other than 0, you can simply add your non-root user to right user group. To do so:

  1. As before, follow the instructions in the Remote - Containers documentation to create a non-root user with sudo access if you do not already have one (though sudo is not required if you start the container itself as root as shown here).

  2. Follow the directions in the section on root access to install the Docker CLI.

  3. Update your devcontainer.json from above so VS Code doesn't override the container's entrypoint and enables the non-root user:

    "runArgs": ["--init"],
    "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind" ]
    "remoteUser": "vscode",
    "overrideCommand": false
  4. Next, update your Dockerfile as follows to wire up an entrypoint that creates a group with the right group ID and be sure the user is in it:

    ARG NONROOT_USER=vscode
    
    RUN echo -e "#!/bin/sh\n\
        sudoIf() { if [ \"\$(id -u)\" -ne 0 ]; then sudo \"\$@\"; else \"\$@\"; fi }\n\
        SOCKET_GID=\$(stat -c '%g' /var/run/docker.sock) \n\
        if [ \"${SOCKET_GID}\" != '0' ]; then\n\
            if [ \"\$(cat /etc/group | grep :\${SOCKET_GID}:)\" = '' ]; then sudoIf groupadd --gid \${SOCKET_GID} docker-host; fi \n\
            if [ \"\$(id ${NONROOT_USER} | grep -E \"groups=.*(=|,)\${SOCKET_GID}\(\")\" = '' ]; then sudoIf usermod -aG \${SOCKET_GID} ${NONROOT_USER}; fi\n\
        fi\n\
        exec \"\$@\"" > /usr/local/share/docker-init.sh \
        && chmod +x /usr/local/share/docker-init.sh
    
    # VS Code overrides ENTRYPOINT and CMD when executing `docker run` by default.
    # Setting the ENTRYPOINT to docker-init.sh will configure non-root access to
    # the Docker socket if "overrideCommand": false is set in devcontainer.json.
    # The script will also execute CMD if you need to alter startup behaviors.
    ENTRYPOINT [ "/usr/local/share/docker-init.sh" ]
    CMD [ "sleep", "infinity" ]
  5. Press F1 and run Remote-Containers: Rebuild Container so the changes take effect.

Final fallback: socat

However, if the host's socket is owned by the root user and root group (root root), you'll need to either change the group on the socket on the host or use socat to proxy the Docker socket without affecting its permissions. The socat option can be safer than updating the permissions of the host socket itself since this would apply to all containers. You can also alias docker to be sudo docker in a .bashrc file, but this does not work in cases where the Docker socket is accessed directly.

Follow these directions to set up non-root access using socat:

  1. Follow the instructions in the Remote - Containers documentation to create a non-root user with sudo access if you do not already have one (though sudo is not required if you start the container itself as root as shown here).

  2. Follow the directions in the section on root access to install the Docker CLI.

  3. Update your devcontainer.json to mount the Docker socket to docker-host.sock in the container and ensure VS Code enables the non-root user, but does not override the entrypoint:

    "runArgs": ["--init"],
    "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind" ],
    "remoteUser": "vscode",
    "overrideCommand": false
  4. Next, add the following to your Dockerfile to wire up socat:

    ARG NONROOT_USER=vscode
    
    # Default to root only access to the Docker socket, set up non-root init script
    RUN touch /var/run/docker-host.sock \
        && ln -s /var/run/docker-host.sock /var/run/docker.sock \
        && apt-get update \
        && apt-get -y install socat
    
    # Create docker-init.sh to spin up socat
    RUN echo "#!/bin/sh\n\
        sudoIf() { if [ \"\$(id -u)\" -ne 0 ]; then sudo \"\$@\"; else \"\$@\"; fi }\n\
        sudoIf rm -rf /var/run/docker.sock\n\
        ((sudoIf socat UNIX-LISTEN:/var/run/docker.sock,fork,mode=660,user=${NONROOT_USER} UNIX-CONNECT:/var/run/docker-host.sock) 2>&1 >> /tmp/vscr-docker-from-docker.log) & > /dev/null\n\
        \"\$@\"" >> /usr/local/share/docker-init.sh \
        && chmod +x /usr/local/share/docker-init.sh
    
    # VS Code overrides ENTRYPOINT and CMD when executing `docker run` by default.
    # Setting the ENTRYPOINT to docker-init.sh will configure non-root access to
    # the Docker socket if "overrideCommand": false is set in devcontainer.json.
    # The script will also execute CMD if you need to alter startup behaviors.
    ENTRYPOINT [ "/usr/local/share/docker-init.sh" ]
    CMD [ "sleep", "infinity" ]
  5. Press F1 and run Remote-Containers: Rebuild Container so the changes take effect.

That's it!

Using bind mounts when working with Docker inside the container

Note: If you need to mount folders within the dev container into your own containers using docker-from-docker, so you may find Docker in Docker meets your needs better in some cases (despite a potential performance penalty).

In some cases, you may want to be able to mount the local workspace folder into a container you create while running from inside the dev container (e.g. using -v from the Docker CLI). The issue is that, with "Docker from Docker", containers are always created on the host. So, when you bind mount a folder into any container, you'll need to use the host's paths.

In GitHub Codespaces, the workspace folder is available in the same place on the host as it is in the container, so you can bind workspace contents as you would normally.

However, for Remote - Containers, this is typically not the case. A simple way to work around this is to put ${localWorkspaceFolder} in an environment variable that you then use when doing bind mounts inside the container.

Add the following to devcontainer.json:

"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }

Then reference the env var when running Docker commands from the terminal inside the container.

docker run -it --rm -v "${LOCAL_WORKSPACE_FOLDER//\\/\/}:/workspace" debian bash

Using this definition

There are no special setup steps are required, but note that the included .devcontainer/Dockerfile can be altered to work with other Debian/Ubuntu-based container images such as node or python. Just, update the FROM statement to reference the new base image. For example, you could use the pre-built mcr.microsoft.com/vscode/devcontainers/python:3 image:

FROM mcr.microsoft.com/vscode/devcontainers/python:3

Beyond that, just follow these steps to use the definition:

  1. If this is your first time using a development container, please see getting started information on setting up Remote-Containers or creating a codespace using GitHub Codespaces.

  2. Start VS Code and open your project folder or connect to a codespace.

  3. Press F1 select and Add Development Container Configuration Files... command for Remote-Containers or Codespaces.

    Note: If needed, you can drag-and-drop the .devcontainer folder from this sub-folder in a locally cloned copy of this repository into the VS Code file explorer instead of using the command.

  4. Select this definition. You may also need to select Show All Definitions... for it to appear.

  5. Finally, press F1 and run Remote-Containers: Reopen Folder in Container or Codespaces: Rebuild Container to start using the definition.

License

Copyright (c) Microsoft Corporation. All rights reserved.

Licensed under the MIT License. See LICENSE.