- Advanced Container Configuration
- Adding environment variables
- Adding another local file mount
- Persist bash history between runs
- Changing the default source code mount
- Improving container disk performance
- Avoiding extension reinstalls on container rebuild
- Adding a non-root user to your dev container
- Setting the project name for Docker Compose
- Using Docker or Kubernetes from a container
- Connecting to multiple containers at once
- Developing inside a container on a remote Docker host
- Reducing Dockerfile build warnings
- Questions or feedback
Advanced Container Configuration
This article includes advanced setup scenarios for the Visual Studio Code Remote - Containers extension. See the Developing inside a Container article for additional information.
Adding environment variables
You can set environment variables in your container without altering the container image by using one of the options below. However, you should verify Terminal > Integrated: Inherit Env is checked in settings or the variables you set may not appear in the Integrated Terminal.
Option 1: Add individual variables
Depending on what you reference in devcontainer.json
:
Dockerfile or image: Add the
containerEnv
property todevcontainer.json
to set variables that should apply to the entire container orremoteEnv
to set variables for VS Code and related sub-processes (terminals, tasks, debugging, etc):"containerEnv": {
"MY_CONTAINER_VAR": "some-value-here",
"MY_CONTAINER_VAR2": "${localEnv:SOME_LOCAL_VAR}"
},
"remoteEnv": {
"PATH": "${containerEnv:PATH}:/some/other/path",
"MY_REMOTE_VARIABLE": "some-other-value-here",
"MY_REMOTE_VARIABLE2": "${localEnv:SOME_LOCAL_VAR}"
}
As this example illustrates,
containerEnv
can reference local variables andremoteEnv
can reference both local and existing container variables.Docker Compose: Since Docker Compose has built-in support for updating container-wide variables, only
remoteEnv
is supported indevcontainer.json
:"remoteEnv": {
"PATH": "${containerEnv:PATH}:/some/other/path",
"MY_REMOTE_VARIABLE": "some-other-value-here",
"MY_REMOTE_VARIABLE2": "${localEnv:SOME_LOCAL_VAR}"
}
As this example illustrates,
remoteEnv
can reference both local and existing container variables.To update variables that apply to the entire container, update (or extend) your
docker-compose.yml
with the following for the appropriate service:version: '3'
services:
your-service-name-here:
environment:
- YOUR_ENV_VAR_NAME=your-value-goes-here
- ANOTHER_VAR=another-value
# ...
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Option 2: Use an env file
If you have a large number of environment variables that you need to set, you can use a .env
file instead. VS Code will automatically pick up a file called .env
in your workspace root, but you can also create one in another location.
First, create an environment file somewhere in your source tree. Consider this .devcontainer/devcontainer.env
file:
YOUR_ENV_VAR_NAME=your-value-goes-here
ANOTHER_ENV_VAR_NAME=your-value-goes-here
Next, depending on what you reference in devcontainer.json
:
Dockerfile or image: Edit
devcontainer.json
and add a path to the.env
file relative to the location ofdevcontainer.json
:"runArgs": ["--env-file","devcontainer.env"]
Docker Compose: Edit
docker-compose.yml
and add a path to the.env
file relative to the Docker Compose file:version: '3'
services:
your-service-name-here:
env_file: devcontainer.env
# ...
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Adding another local file mount
You can add a volume bound to any local folder by using the following appropriate steps, based on what you reference in devcontainer.json
:
Dockerfile or image: Add the following to the
mounts
property (VS Code 1.41+) in this same file:"mounts": [
"source=/local/source/path/goes/here,target=/target/path/in/container/goes/here,type=bind,consistency=cached"
]
You can also reference local environment variables or the local path of the workspace. For example, this will bind mount
~
($HOME
) on macOS/Linux and the user’s folder (%USERPROFILE%
) on Windows and a sub-folder in the workspace to a different location:"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE},target=/host-home-folder,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/app-data,target=/data,type=bind,consistency=cached"
]
Docker Compose: Update (or extend) your
docker-compose.yml
with the following for the appropriate service:version: '3'
services:
your-service-name-here:
volumes:
- /local/source/path/goes/here:/target/path/in/container/goes/here:cached
- ~:/host-home-folder:cached
- ./data-subfolder:/data:cached
# ...
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Persist bash history between runs
You can also use a mount to persist your bash command history across sessions / container rebuilds.
First, update your Dockerfile
so that each time a command is used in bash
, the history is updated and stored in a location we will persist. Replace user-name-goes-here
with the name of a non-root user in the container (if one exists).
ARG USERNAME=user-name-goes-here
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
&& echo $SNIPPET >> "/root/.bashrc" \
# [Optional] If you have a non-root user
&& mkdir /commandhistory \
&& touch /commandhistory/.bash_history \
&& chown -R $USERNAME /commandhistory \
&& echo $SNIPPET >> "/home/$USERNAME/.bashrc" \
Next, add a local volume to store the command history. This step varies depending on whether or not you are using Docker Compose.
Dockerfile or image: Use the
mounts
property (VS Code 1.41+) in yourdevcontainer.json
file. Replaceuser-name-goes-here
with the name of a non-root user in the container (if one exists)."mounts": [
"source=projectname-bashhistory,target=/commandhistory,type=volume"
]
Docker Compose: Update (or extend) your
docker-compose.yml
with the following for the appropriate service. Replaceuser-name-goes-here
with the name of a non-root user in the container (if one exists).version: '3'
services:
your-service-name-here:
volumes:
- projectname-bashhistory:/commandhistory
# ...
volumes:
projectname-bashhistory:
Finally, if you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Changing the default source code mount
If you add the image
or dockerFile
properties to devcontainer.json
, VS Code will automatically “bind” mount your current workspace folder into the container. If git
is present on the host’s PATH
and the folder containing ./devcontainer/devcontainer.json
is within a git
repository, the current workspace mounted will be the root of the repository. If git
is not present on the host’s PATH
, the current workspace mounted will be the folder containing ./devcontainer/devcontainer.json
.
While this is convenient, you may want to change mount settings, alter the type of mount, location, or run in a remote container.
You can use the workspaceMount
property in devcontainer.json
to change the automatic mounting behavior. It expects the same value as the Docker CLI --mount
flag.
For example:
"workspaceMount": "source=${localWorkspaceFolder}/sub-folder,target=/workspace,type=bind,consistency=delegated",
"workspaceFolder": "/workspace"
This also allows you to do something like a named volume mount instead of a bind mount, which can be useful particularly when using a remote Docker Host or you want to store your entire source tree in a volume.
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Improving container disk performance
The Remote - Containers extension uses “bind mounts” to source code in your local filesystem by default. While this is the simplest option, on macOS and Windows, you may encounter slower disk performance when running commands like yarn install
from inside the container. There are few things you can do to resolve these type of issues.
Store your source code in the WSL2 filesystem on Windows
Windows 10 2004 and up includes an improved version of the Windows Subsystem for Linux (WSL2) that provides a full Linux kernel and has significantly improved performance over WSL1. Docker Desktop 2.3+ includes a new WSL2 Engine that runs Docker in WSL rather than in a VM. Therefore, if you store your source code in the WSL2 filesystem, you will see improved performance along with better compatibility for things like setting permissions.
See Open a WSL2 folder in a container on Windows for details on using this new engine from VS Code.
Update the mount consistency to ‘delegated’ for macOS
By default, the Remote - Containers extension uses the Docker cached mount consistency on macOS since this provides a good mix between performance and write guarantees on the host OS. However, you can opt to use the delegated
consistency instead if you do not expect to be writing to the same file in both locations very often.
When using a Dockerfile or image, update the Remote > Containers: Workspace Mount Consistency property in settings to delegated
:
When using Docker Compose, update your local bind mount in docker-compose.yml
as follows:
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
- .:/workspace:delegated
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Use Open Repository in a Container
The Remote-Containers: Open Repository in a Container… command uses an isolated, local Docker named volume instead binding to the local filesystem. In addition to not polluting your file tree, local volumes have the added benefit of improved performance on Windows and macOS.
See Open a Repository in a Container for details on using this approach.
The next two sections will outline how to use a named volume in other scenarios.
Use a targeted named volume
Since macOS and Windows run containers in a VM, “bind” mounts are not as fast as using the container’s filesystem directly. Fortunately, Docker has the concept of a local “named volume” that can act like the container’s filesystem but survives container rebuilds. This makes it ideal for storing package folders like node_modules
, data folders, or output folders like build
where write performance is critical. Follow the appropriate steps below based on what you reference in devcontainer.json
.
Dockerfile or image:
Let’s use the vscode-remote-try-node repository to illustrate how to speed up yarn install
.
Follow these steps:
Use the
workspaceMount
property indevcontainer.json
to tell VS Code where to bind your source code. Then use themounts
property (VS Code 1.41+) to mount thenode_modules
sub-folder into a named local volume instead."mounts": [
"source=try-node-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
]
Since this repository runs VS Code as the non-root “node” user, we need to add a
postCreateCommand
to be sure the user can access the folder."remoteUser": "node",
"mounts": [
"source=try-node-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
],
"postCreateCommand": "sudo chown node node_modules"
This second step is not required if you will be running in the container as
root
.
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Two notes on this approach:
If you delete the
node_modules
folder in the container, it may lose the connection to the volume. Delete the contents of thenode_modules
folder instead when needed (rm -rf node_modules/* node_modules/.*
).You’ll find that an empty
node_modules
folder gets created locally with this method. This is because the volume mount point in the container is inside the local filesystem bind mount. This is expected and harmless.
Docker Compose:
While vscode-remote-try-node does not use Docker Compose, the steps are similar, but the volume mount configuration is placed in a different file.
In your Docker Compose file (or an extended one), add a named local volume mount to the
node_modules
sub-folder for the appropriate service(s). For example:version: '3'
services:
your-service-name-here:
volumes:
# Or wherever you've mounted your source code
- .:/workspace:cached
- try-node-node_modules:/workspace/node_modules
# ...
volumes:
try-node-node_modules:
Next, be sure the
workspaceFolder
property indevcontainer.json
matches the place your actual source code is mounted:"workspaceFolder": "/workspace"
If you’re running in the container with a user other than root, add a
postCreateCommand
to update the owner of the folder you mount since it may have been mounted as root. Replaceuser-name-goes-here
with the appropriate user."remoteUser": "node",
"workspaceFolder": "/workspace",
"postCreateCommand": "sudo chown user-name-goes-here node_modules"
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Use a named volume for your entire source tree
Finally, if none of the above options meet your needs, you can go one step further and clone your entire source tree inside of a named volume rather than locally. You can set up a named volume by taking an existing devcontainer.json
configuration and modifying it as follows (updating your-volume-name-here
with whatever you want to call the volume).
Depending on what you reference in devcontainer.json
:
Dockerfile or image: Use the following properties in
devcontainer.json
to mount a local named volume into the container:"workspaceMount": "source=your-volume-name-here,target=/workspace,type=volume"
"workspaceFolder": "/workspace",
Docker Compose: Update (or extend) your
docker-compose.yml
with the following for the appropriate service(s):version: '3'
services:
your-service-name-here:
volumes:
- your-volume-name-here:/workspace
# ...
volumes:
your-volume-name-here:
You’ll also want to be sure the
workspaceFolder
property indevcontainer.json
matches the place the volume is mounted (or a sub-folder inside the volume):"workspaceFolder": "/workspace"
If you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Next, either use the Git: Clone command from the Command Palette or start an integrated terminal (kb(workbench.action.terminal.new)
) and use the git clone
command to clone your source code into the /workspace
folder.
Finally, use the File > Open… / Open Folder… command to open the cloned repository in the container.
Avoiding extension reinstalls on container rebuild
By default, VS Code will install extensions and VS Code Server inside the container’s filesystem. While this has performance benefits over a locally mounted filesystem, the disadvantage is that VS Code will have to reinstall them on a container rebuild. If you find yourself rebuilding frequently, you can use a local “named volume” mount so that the extensions and VS Code Server survive a container rebuild.
There are a two side effects of doing this you should be aware of:
- Deleting the container will not automatically delete the named volume.
- Sharing the volume across multiple containers can have unintended consequences, so to be safe we will pick a unique name for each.
To create the named local volume, follow these steps:
If you are running as a non-root user, you’ll need to ensure your Dockerfile creates
~/.vscode-server/extensions
and/or~/.vscode-server-insiders/extensions
in the container with this non-root user as the owner. If you do not do this, the folder will be owned by root and your connection will fail with a permissions issue. See Adding a non-root user to your dev container for full details, but you can use this snippet in your Dockerfile to create the folders. Replaceuser-name-goes-here
with the actual user name:ARG USERNAME=user-name-goes-here
RUN mkdir -p /home/$USERNAME/.vscode-server/extensions \
/home/$USERNAME/.vscode-server-insiders/extensions \
&& chown -R $USERNAME \
/home/$USERNAME/.vscode-server \
/home/$USERNAME/.vscode-server-insiders
Next, we’ll configure a named volume mount for
~/.vscode-server/extensions
and~/.vscode-server-insiders/extensions
in the container. The configuration will depend on whether you specify an image, Dockerfile, or Docker Compose file in yourdevcontainer.json
file.Dockerfile or image:
Add the following to
devcontainer.json
, replacing/root
with the home directory in the container if not root (for example/home/user-name-goes-here
) andunique-vol-name-here
with a unique name for the volume:"mounts": [
"source=unique-vol-name-here,target=/root/.vscode-server/extensions,type=volume",
// And/or for VS Code Insiders
"source=unique-vol-name-here-insiders,target=/root/.vscode-server-insiders/extensions,type=volume",
]
Docker Compose:
Update (or extend) your
docker-compose.yml
with the following for the appropriate service. Replaceunique-vol-name-here
with a unique name for the volume.services:
your-service-name-here:
volumes:
- unique-vol-name-here:~/.vscode-server/extensions
# And/or for VS Code Insiders
- unique-vol-name-here-insiders:~/.vscode-server-insiders/extensions
# ...
volumes:
unique-vol-name-here:
unique-vol-name-here-insiders:
Finally, if you’ve already built the container and connected to it, you’ll need to run Remote-Containers: Rebuild Container from the Command Palette (
kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Reopen Folder in Container to connect to the container for the first time.
After the container is up and running, subsequent rebuilds will not reacquire any extensions or the VS Code server. The build will also not use the latest extensions list from devcontainer.json
.
However, if you want to completely reset, you can delete the volume and everything will be reinstalled on restart.
docker volume rm unique-vol-name-here
Adding a non-root user to your dev container
Many Docker images use root as the default user, but there are cases where you may prefer to use a non-root user instead. If you do so, there are some quirks with local filesystem (bind) mounts that you should know about. Specifically:
Docker Desktop for Mac: Inside the container, any mounted files/folders will act as if they are owned by the container user you specify. Locally, all filesystem operations will use the permissions of your local user instead.
Docker Desktop for Windows: Inside the container, any mounted files/folders will appear as if they are owned by
root
but the user you specify will still be able to read/write them and all files will be executable. Locally, all filesystem operations will use the permissions of your local user instead. This is because there is fundamentally no way to directly map Windows-style file permissions to Linux.Docker CE/EE on Linux: Inside the container, any mounted files/folders will have the exact same permissions as outside the container - including the owner user ID (UID) and group ID (GID). Because of this, your container user will either need to have the same UID or be in a group with the same GID. The actual name of the user / group does not matter. The first user on a machine typically gets a UID of 1000, so most containers use this as the ID of the user to try to avoid this problem.
Specifying a user for VS Code
If the image or Dockerfile you are using already provides an optional non-root user (like the node
image) but still defaults to root, you can opt into having VS Code (server) and any sub-processes (terminals, tasks, debugging) use it by specifying the remoteUser
property in devcontainer.json
:
"remoteUser": "user-name-goes-here"
On Linux, if you are referencing a Dockerfile or image in devcontainer.json
, this will also automatically update the container user’s UID/GID to match your local user to avoid the bind mount permissions problem that exists in this environment (unless you set "updateRemoteUserUID": false
). In the Docker Compose case, the container user’s UID/GID will not be updated but you can manually change these values in a Dockerfile.
Since this setting only affects VS Code and related sub-processes, VS Code needs to be restarted (or the window reloaded) for it to take effect. However, UID/GID updates are only applied when the container is created and requires a rebuild to change.
Specifying the default container user
In some cases, you may need all processes in the container to run as a different user (for example, due to startup requirements) rather than just VS Code. How you do this varies slightly depending on whether or not you are using Docker Compose.
Dockerfile and image: Add the
containerUser
property to this same file."containerUser": "user-name-goes-here"
On Linux, like
remoteUser
, this will also automatically update the container user’s UID/GID to match your local user to avoid the bind mount permissions problem that exists in this environment (unless you set"updateRemoteUserUID": false
).Docker Compose: Update (or extend) your
docker-compose.yml
with the following for the appropriate service:user: user-name-or-UID-goes-here
Creating a non-root user
While any images or Dockerfiles that come from the Remote - Containers extension will include a non-root user with a UID/GID of 1000 (typically either called vscode
or node
), many base images and Dockerfiles do not. Fortunately, you can update or create a Dockerfile that adds a non-root user into your container.
Running your application as a non-root user is recommended even in production (since it is more secure), so this is a good idea even if you’re reusing an existing Dockerfile. For example, this snippet for a Debian/Ubuntu container will create a user called user-name-goes-here
, give it the ability to use sudo
, and set it as the default:
ARG USERNAME=user-name-goes-here
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# Create the user
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
#
# [Optional] Add sudo support. Omit if you don't need to install software after connecting.
&& apt-get update \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
# ********************************************************
# * Anything else you want to do like clean up goes here *
# ********************************************************
# [Optional] Set the default user. Omit if you want to keep the default as root.
USER $USERNAME
Tip: If you hit an error when building about the GID or UID already existing, the image you selected likely already has a non-root user you can take advantage of directly.
In either case, if you’ve already built the container and connected to it, run Remote-Containers: Rebuild Container from the Command Palette (kbstyle(F1)
) to pick up the change. Otherwise run Remote-Containers: Open Folder in Container… to connect to the container.
Change the UID/GID of an existing container user
While the remoteUser
property tries to automatically update the UID/GID as appropriate on Linux when using a Dockerfile or image, you can use this snippet in your Dockerfile to manually change the UID/GID of a user instead. Update the ARG
values as appropriate.
ARG USERNAME=user-name-goes-here
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupmod --gid $USER_GID $USERNAME \
&& usermod --uid $USER_UID --gid $USER_GID $USERNAME \
&& chown -R $USER_UID:$USER_GID /home/$USERNAME
Note that on Alpine Linux, you’ll need to install the shadow
package first.
RUN apk add --no-cache shadow
Setting the project name for Docker Compose
VS Code will respect the value of the COMPOSE_PROJECT_NAME environment variable if set for the VS Code process or in a .env
file in the root of the project.
For example, after shutting down all VS Code windows, you can start VS Code from the command line as follows:
# from bash
COMPOSE_PROJECT_NAME=foo code .
# from PowerShell
$env:COMPOSE_PROJECT_NAME=foo
code .
Or add the following to a .env
file in the root of the project (not in the .devcontainer
folder):
COMPOSE_PROJECT_NAME=foo
Using Docker or Kubernetes from a container
While you can build, deploy, and debug your application inside a dev container, you may also need to test it by running it inside a set of production-like containers. Fortunately, by installing the needed Docker or Kubernetes CLIs and mounting your local Docker socket, you can build and deploy your app’s container images from inside your dev container.
Once the needed CLIs are in place, you can also work with the appropriate container cluster using the Docker extension if you force it to run as a Workspace extension or the Kubernetes extension.
See the following example dev containers definitions for additional information on a specific scenario:
Docker-from-Docker - Includes the Docker CLI and illustrates how you can use it to access your local Docker install from inside a dev container by volume mounting the Docker Unix socket.
Docker-from-Docker Compose - Variation of Docker-from-Docker for situations where you are using Docker Compose instead of a single Dockerfile.
Kubernetes-Helm - Includes the Docker CLI, kubectl, and Helm and illustrates how you can use them from inside a dev container to access a local Minikube or Docker provided Kubernetes cluster.
Note that it is possible to actually run the Docker daemon inside a container. While using the docker
image as a base for your container is an easy way to do this, given the downsides and performance implications, using the Docker CLI to access your local Docker host from inside a container is typically a better option.
Mounting host volumes with Docker from inside a container
When using the Docker CLI from inside a container, the host’s Docker daemon is used. This effects mounting directories from inside the container as the path inside the container may not match the path of the directory on the host.
For example:
docker run -v /workspace/examplefile.txt:/incontainer/path busybox
This will fail as the path on the host, outside the container isn’t /workspace/...
. To work around this issue, you can pass the host directory into the container as an environment variable as follows in devcontainer.json
:
"remoteEnv": {
// Pass in the host directory for Docker mount commands from inside the container
"HOST_PROJECT_PATH": "${localWorkspaceFolder}"
}
The example below is from a makefile
and mounts the KUBECONFIG
file from the development container into the new Docker container it starts:
docker run -p 8089:8089 -p 9090:9090 -v $(shell echo ${KUBECONFIG} | sed s#/workspace#${HOST_PROJECT_PATH}#):/kubeconfig.json -e KUBECONFIG=/kubeconfig.json ${IMG} -f behaviours/run_submit_locust.py
Connecting to multiple containers at once
Currently you can only connect to one container per VS Code window. However, you can spin up multiple VS Code windows to attach to them.
If you’d prefer to use devcontainer.json
instead and are using Docker Compose, you can create separate devcontainer.json
files for each service in your source tree that point to a common docker-compose.yml
.
To see how this works, consider this example source tree:
📁 project-root
📁 .git
📁 container1-src
📄 .devcontainer.json
📄 hello.go
📁 container2-src
📄 .devcontainer.json
📄 hello.js
📄 docker-compose.yml
The location of the .git
folder is important, since we will need to ensure the containers can see this path for source control to work properly.
Next, assume the docker-compose.yml
in the root is as follows:
version: '3'
services:
container-1:
image: ubuntu:bionic
volumes:
# Mount the root folder that contains .git
- .:/workspace:cached
command: /bin/sh -c "while sleep 1000; do :; done"
links:
- container-2
# ...
container-2:
image: ubuntu:bionic
volumes:
# Mount the root folder that contains .git
- .:/workspace:cached
command: /bin/sh -c "while sleep 1000; do :; done"
# ...
You can then set up container1-src/.devcontainer.json
for Go development as follows:
{
"name": "Container 1",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "container-1",
"shutdownAction": "none",
"extensions": ["golang.go"],
// Open the sub-folder with the source code
"workspaceFolder": "/workspace/container1-src",
}
Next, you can container2-src/.devcontainer.json
for Node.js development by changing workspaceFolder
and installing Node.js extensions:
{
"name": "Container 2",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "container-2",
"shutdownAction": "none",
"extensions": ["dbaeumer.vscode-eslint"],
"workspaceFolder": "/workspace/container2-src"
}
The "shutdownAction":"none"
in the devcontainer.json
files is optional, but will leave the containers running when VS Code closes — which prevents you from accidentally shutting down both containers by closing one window.
To connect to both:
- Run Remote-Containers: Open Folder in Container… from the Command Palette (
kbstyle(F1)
) and select thecontainer1-src
folder. - VS Code will then start up both containers, connect this window to service
container-1
, and install the Go extension. - Next, start up a new window using File > New Window.
- In the new window, run Remote-Containers: Open Folder in Container… from the Command Palette (
kbstyle(F1)
) and select thecontainer2-src
folder. - Since the services are already running, VS Code will then connect to
container-2
and install the ESLint extension.
You can now interact with both containers at once from separate windows.
Extending a Docker Compose file when connecting to two containers
If you want to extend your Docker Compose file for development, you should use a single docker-compose.yml
that extends both services (as needed) and is referenced in both .devcontainer.json
files.
For example, consider this docker-compose.devcontainer.yml
file:
version: '3'
services:
container-1:
volumes:
- ~:~/local-home-folder:cached # Additional bind mount
# ...
container-2:
volumes:
- ~/some-folder:~/some-folder:cached # Additional bind mount
# ...
Both .devcontainer.json
files would be updated as follows:
"dockerComposeFile": [
"../docker-compose.yml",
"../docker-compose.devcontainer.yml",
]
This list of compose files is used when starting the containers, so referencing different files in each .devcontainer.json
can have unexpected results.
Developing inside a container on a remote Docker host
Sometimes you may want to use the Remote - Containers extension to develop inside a container that sits on a remote server. Docker does not support mounting (binding) your local filesystem into a remote container, so VS Code’s default devcontainer.json
behavior to use your local source code will not work. While this is the default behavior, in this section we will cover connecting to a remote host so that you can either attach to any running container, or use a local devcontainer.json
file as a way to configure, create, and connect to a remote dev container.
However, note that the Docker CLI still needs to be installed locally (along with the Docker Compose CLI if you are using it).
A basic remote example
Setting up VS Code to attach to a container on a remote Docker host can be as easy as setting the docker.host
property in settings.json
and restarting VS Code (or reloading the window). For example:
"docker.host":"ssh://your-remote-user@your-remote-machine-fqdn-or-ip-here"
Using SSH requires a supported SSH client, that you have key based authentication configured for the remote host, and that the key is imported into your local SSH agent. See the article on using SSH Keys with Git for details on configuring the agent and adding your key.
At this point, you can attach to containers on the remote host. We’ll cover more on information on how you can connect using settings and environment variables or Docker Machine later in this section.
For devcontainer.json
, there is one additional step: You’ll need to update any configured (or auto-configured) bind mounts so they no longer point to the local filesystem.
There’s two variations of this setup. The first is to create your remote dev container first, and then clone your source code into a named volume since this does not require you to have direct access to the filesystem on the remote host.
Here is a basic devcontainer.json
example of this setup:
{
"image": "node", // Or "dockerFile"
"workspaceFolder": "/workspace",
"workspaceMount": "source=remote-workspace,target=/workspace,type=volume"
}
In fact, the Remote-Containers: Open Repository in Container… command in the Command Palette (kbstyle(F1)
) uses this same technique. If you already have a devcontainer.json
file in a GitHub repository that references an image or Dockerfile, the command will automatically use a named volume instead of a bind mount - which also works with remote hosts.
The second approach is to bind mount a folder on the remote machine into your container. This requires you to have access to the remote filesystem, but also allows you to work with existing source code on the remote machine.
Update the workspaceMount
property in the example above to use this model instead:
"workspaceMount": "source=/absolute/path/on/remote/machine,target=/workspace,type=bind,consistency=cached"
In either case, to try it out, run Remote-Containers: Open Folder in Container…, and select the local folder with the .devcontainer.json
file in it.
See Converting an existing or pre-defined devcontainer.json for information on other scenarios like Docker Compose.
Connect using VS Code settings or local environment variables
If you already have a remote Docker host up and running, you can use the following properties in your workspace or user settings.json
to specify the host.
The SSH protocol
Recent versions of Docker (18.06+) have added support for the SSH protocol to connect to remote Docker Host. This is easy to configure as you only need to set one property in settings.json
to use it.
First, install a supported SSH client, configure key based authentication), and then import your key into your local SSH agent (which often is not running by default on Windows and Linux). See the article on using SSH Keys with Git for details on configuring the agent and adding the key.
Then, add the following to settings.json
(replacing values as appropriate):
"docker.host":"ssh://your-remote-user@your-remote-machine-fqdn-or-ip-here"
After restarting VS Code (or reloading the window), you will now be able to attach to any running container on the remote host. You can also use specialized, local devcontainer.json
files to create / connect to a remote dev container.
Tip: If this is not working for you but you are able to connect to the host using SSH from the command line, be sure you have the SSH agent running with your authentication key. If all else fails, you can use an SSH tunnel as a fallback instead.
Using the TCP protocol
While the SSH protocol has its own built-in authorization mechanism, using the TCP protocol often requires setting other properties. These are:
"docker.host":"tcp://your-remote-machine-fqdn-or-ip-here:port",
"docker.certPath": "/optional/path/to/folder/with/certificate/files",
"docker.tlsVerify": "1" // or "0"
As with SSH, restart VS Code (or reload the window) for the settings to take effect.
Using environment variables instead of settings.json
If you’d prefer not to use settings.json
, you can set environment variables in a terminal instead. The steps to do so are:
- Shut down all instances of VS Code.
- Ensure VS Code is in your operating system
PATH
. - Set the environment variables (for example
DOCKER_HOST
) in a terminal / command prompt. - Type
code
in this same terminal / command prompt to launch VS Code with the variables set.
Connect using Docker Machine
Docker Machine is a CLI that allows you to securely set up remote Docker hosts and connect to them. You should also be aware that drivers like the generic driver shown below will require that any non-root user you specify has passwordless-sudo privileges.
Use the following command with the appropriate values to set up Docker on a remote SSH host. Note that you can use alternate Docker Machine drivers instead if you prefer.
docker-machine create --driver generic --generic-ip-address your-ip-address-here --generic-ssh-user your-remote-user-here give-it-a-name-here
Once you have a machine set up:
Shut down all instances of VS Code.
Ensure VS Code is in your operating system
PATH
.Execute one of the following commands for your OS:
macOS or Linux:
eval $(docker-machine env give-it-a-name-here)
code
Windows PowerShell:
docker-machine env give-it-a-name-here | Invoke-Expression
code
Converting an existing or pre-defined devcontainer.json
To convert an existing or pre-defined, local devcontainer.json
into a remote one, follow these steps:
Open a local folder in VS Code (not a remote one) where you want to convert the file.
If you did not select a folder with a
devcontainer.json
in it, you can pick a pre-defined one by running Remote-Containers: Add Container Configuration File… from the Command Palette (kbstyle(F1)
).Follow these steps based on what your
.devcontainer/devcontainer.json
or.devcontainer.json
references to alter the source code mount:Dockerfile or image:
If you do not have login access to the remote host, use a Docker “volume” for your source code. Update
.devcontainer/devcontainer.json
as follows (replacingremote-workspace
with a unique volume name if desired):"workspaceMount": "source=remote-workspace,target=/workspace,type=volume"
"workspaceFolder": "/workspace",
If you do have login access, you can use a remote filesystem bind mount instead:
"workspaceMount": "source=/absolute/path/on/remote/machine,target=/workspace,type=bind,consistency=cached"
"workspaceFolder": "/workspace",
The
workspaceMount
property supports the same values as the Docker CLI--mount
flag if you have a different scenario in mind.Docker Compose:
If you do not have login access to the remote host, update (or extend) your
docker-compose.yml
. Replaceyour-service-name-here
with the value specified for the"service"
property indevcontainer.json
and appropriate andremote-workspace
with a unique volume name:version: '3'
services:
your-service-name-here:
volumes:
- remote-workspace:/workspace
# ...
volumes:
remote-workspace:
If you do have login access, you can use a remote filesystem bind mount instead:
version: '3'
services:
your-service-name-here:
volumes:
- /absolute/path/on/remote/machine:/workspace:cached
# ...
See the Docker Compose documentation on
volumes
if you need to support a different scenario.Run the Remote-Containers: Reopen Folder in Container command from the Command Palette (
kbstyle(F1)
) or Remote-Containers: Rebuild Container.If you used a volume instead of a bind mount, use
kb(workbench.action.terminal.new)
to open a terminal inside the container. You can rungit clone
from here to pull down your source code and use File > Open… / Open Folder… to open the cloned repository.
Next time you want to connect to this same container, run Remote-Containers: Open Folder in Container… and select the same local folder in a VS Code window.
[Optional] Making the remote source code available locally
If you store your source code on the remote host’s filesystem instead of inside a Docker volume, there are several ways you can access the files locally:
- Mount the remote filesystem using SSHFS.
- Sync files from the remote host to your local machine using
rsync
. - Use the mount command if you are using Docker Machine.
Using SSHFS or Docker Machine’s mount command are the more convenient options and do not require any file sync’ing. However, performance will be significantly slower than working through VS Code, so they are best used for single file edits and uploading/downloading content. If you need to use an application that bulk reads/write to many files at once (like a local source control tool), rsync is a better choice.
Reducing Dockerfile build warnings
The following are some tips for eliminating warnings that may be appearing in your Dockerfile builds.
debconf: delaying package configuration, since apt-utils is not installed
This error can typically be safely ignored and is tricky to get rid of completely. However, you can reduce it to one message in stdout when installing the needed package by adding the following to your Dockerfile:
# Configure apt
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends apt-utils dialog 2>&1
## YOUR DOCKERFILE CONTENT GOES HERE
ENV DEBIAN_FRONTEND=dialog
Warning: apt-key output should not be parsed (stdout is not a terminal)
This non-critical warning tells you not to parse the output of apt-key
, so as long as your script doesn’t, there’s no problem. You can safely ignore it.
This occurs in Dockerfiles because the apt-key
command is not running from a terminal. Unfortunately, this error cannot be eliminated completely, but can be hidden unless the apt-key
command returns a non-zero exit code (indicating a failure).
For example:
# (OUT=$(apt-key add - 2>&1) || echo $OUT) will only print the output with non-zero exit code is hit
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT)
You can also set the APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE
environment variable to suppress the warning, but it looks a bit scary so be sure to add comments in your Dockerfile if you use it:
# Suppress an apt-key warning about standard out not being a terminal. Use in this script is safe.
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
Information messages appearing in red
Some CLIs output certain information (like debug details) to standard error instead of standard out. These will appear in red in VS Code’s terminal and output logs.
If the messages are harmless, you can pipe the output of the command from standard error to standard out instead by appending 2>&1
to the end of the command.
For example:
RUN apt-get -y install --no-install-recommends apt-utils dialog 2>&1
If the command fails, you will still be able to see the errors but they won’t be in red.
Questions or feedback
- See Tips and Tricks or the FAQ.
- Search on Stack Overflow.
- Add a feature request or report a problem.
- Create a development container definition for others to use.
- Contribute to our documentation or VS Code itself.
- See our CONTRIBUTING guide for details.