Dev Containers: Isolate your Visual Studio Code workspaces

And how to run Docker within your Dev Containers

Toon Sevrin
5 min readAug 15, 2020

The Remote Containers extension lets you use a Docker container as a full-featured development environment. But what happens if you want to run a Docker container within a Remote Container?

The Remote-Containers extension

Remote Containters allow you to isolate each project’s development environment. A containerized development environment has the following benefits:

  • Reproducibility: Each developer has exactly the same workspace
  • Isolation: Workspaces and their dependencies are isolated from each other
  • Security: Prevents malicious dependencies from installing malware or reading your files.

Each project has a .devcontainer folder. The .devcontainer folder contains the Dockerfile and devcontainer.json configuration for your development environment.

Setting up a development environment using these files is easy and well-documented. Getting Docker to work securely within such an environment however, is not documented at all.

Requirements

Quick Start

Let’s create a sample devcontainer to get a feeling of development containers. You can skip the Quick Start section if you are already familiar with development containers.

To quickly get started:

  • Press ctrl+shift+P to open the command pallet
  • Type Remote-Containers: Try a Sample... (If this is not an option, make sure you close your container with ctrl+K F )
Try a sample container
  • Now choose the language you would like to try out, we will try Go for the Go programming environment. You can select a different environment.
Select the Go programming environment

And there you go, you’ve created a project with a .devcontainer folder. Everything you do in here is now isolated from the rest of your system. If you run malicious code, it will be very difficult for that code to do nasty stuff on your system or steal your credentials.

As you can see the .devcontainer folder contains two files:

  • devcontainer.json: The configuration file that declares which extensions you want, which ports you wish to forward…
  • Dockerfile: The Dockerfile for your development container, you can declare your dependencies (like the go development tools) in here.

If you wish to open an existing project in a development container, make sure a .devcontainer folder exists, press ctrl+shift+P and type remote-containers: Open Folder In Container....

If you want to use a sample .devcontainer folder in an existing project, press ctrl+shift+P and type remote-containers: Add Development Container Configuration Files... . Then you can select your programming language.

Run Docker in a Development Container

Great, you’ve got the basics of how development containers work. You declare a Dockerfile. This Dockerfile defines your development environment.

But even if you type sudo apt install docker.io within your container, you notice that the docker daemon is not running:

$ sudo docker run nginx
docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?.

This is because, by default, you cannot create a Docker environment in a Docker container, due to the way containers work (the daemon needs kernel capabilities).

You now have two options:

  • You can connect your the Docker daemon that is running on your host. This is often recommended but is not secure at all. From your development environment, you can easily compromise the whole host system this way. This is thus not isolated.
  • You can run docker-in-docker: This creates a docker daemon within a docker container

Back in the day docker-in-docker required a container to be ran as privileged (give it all kernel privileges) and as root (allow the user to use all kernel privileges), this of course made escalation out of the container trivial.

Nowadays, we can run docker as a rootless user. This is what we will do. Your container is still ran as privileged (there’s no way around that), but the user no longer has these privileges, so a malicious dependency can’t exploit any of these privileges.

Setting up a rootless Docker-in-Docker development container

To setup Docker-in-Docker (DinD) you create a privileged Dev Container with a rootless user. The container has to be privileged but because the user is rootless, the changes of this being exploited are small.

Let’s get started with setting up a sample container. Note that the dind-rootless image is an Alpine image. You thus have to use the apk package manager.

Edit the .devcontainers/Dockerfilefile for your rootless Docker-in-Docker dev container:

# There may be a newer version
FROM docker:19.03.12-dind-rootless
# Go into root
USER root
# Install your dependencies, this can be changed
RUN apk add git bash curl make vim go
# Set the user back to the rootless user
USER rootless
# Setup docker
ENV DOCKER_HOST=unix:///var/run/user/1000/docker.sock

Now, all though this is ran as a rootless user, we still need to make the container privileged.

Edit the .devcontainers/devcontainer.json file:

{
"name": "Docker",
"dockerFile": "Dockerfile",
"runArgs": [ "--privileged" ],
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"go.gopath": "~/go"
},
"extensions": [
"golang.go",
],
"postCreateCommand": "dockerd-entrypoint.sh --experimental &",
"remoteUser": "rootless"
}

Important changes:

  • “runArgs”: [ “ — privileged” ] : This is always required for docker in docker
  • “postCreateCommand”: “dockerd-entrypoint.sh — experimental &”, : Start the rootless docker daemon when you open the container
  • “remoteUser”: “rootless” : Ensures the container is opened as the rootless user

Give it a shot. Spin up the dev container and type: docker run -it ubuntu . This should put you into a full-fletched ubuntu container. Interestingly enough you do have root privileges inside of this container. By binding to your workdir you can thus do actions that would typically require root privileges.

There you go. When you now open this project in a Dev Container you can run containers without any problem, and they will not appear in your guest daemon!

--

--