DEV Community

Cover image for Setting Up VSCode Development Environments with Docker on Linux
Iñigo Etxaniz
Iñigo Etxaniz

Posted on • Updated on

Setting Up VSCode Development Environments with Docker on Linux

Introduction

In the evolving landscape of software development, the efficiency and reliability of your development environment can significantly impact productivity and collaboration. The VSCode in Docker project recognizes this, offering a versatile approach to set up development environments using Docker to run Visual Studio Code (VSCode) on Linux. This method excels in creating isolated, secure, and highly reproducible development spaces, tailored to the diverse needs of modern developers.

This article delves into the world of Dockerized VSCode environments, underscoring benefits like enhanced security, resource efficiency, and consistency across multiple machines. A unique advantage of this setup is its ability to segregate development environments based on project or language-specific needs. This isolation not only simplifies environment management but also reduces the clutter of unnecessary extensions, avoiding unproductive distractions or annoying notifications.

The project provides Docker images such as inigoetxaniz/vscode-base, inigoetxaniz/vscode-go, and inigoetxaniz/vscode-ext-devel, each catering to different development requirements. This setup is ideal for testing new extensions in a safe, controlled environment, without risking your primary development setup. It also allows for simultaneously running multiple VSCode instances with different configurations and access controls – imagine having separate instances for Node.js and Rust, each with its own tailored settings and themes.

In the upcoming sections, we will explore the advantages of using Dockerized VSCode environments, offer a step-by-step guide for setup, and discuss practical use cases and customization options. Join us as we dive in to discover how Docker can revolutionize your VSCode experience on Linux, enhancing productivity and offering unparalleled flexibility in your development workflow.

Advantages of Dockerized VSCode Environments

Getting Started

Before diving into the setup of Dockerized VSCode environments, it's important to cover the basic requirements. The primary requirement is a Linux-based system with Docker installed. For the purposes of this guide and the development of the Dockerized VSCode project, Ubuntu 22.04 LTS (Long-Term Support) with the GNOME desktop environment has been used as the testing platform. This version of Ubuntu provides a stable and up-to-date foundation, ensuring compatibility and performance for the Dockerized VSCode environments.

While this guide won't delve into the specifics of installing Docker on Ubuntu, users new to Docker can follow the comprehensive installation instructions provided by Docker itself at Docker Installation Guide for Ubuntu. These instructions are clear, detailed, and cater to various setup preferences, ensuring you can get Docker up and running smoothly on your system.

It's important to note that while this guide focuses on Ubuntu 22.04, the principles and steps should be applicable to other Linux distributions with minor adjustments. However, the choice of Ubuntu 22.04 comes with the advantage of long-term support and widespread use, making it a reliable choice for a development environment.

Isolation and Security

One of the standout features of using Dockerized environments for Visual Studio Code (VSCode) is the enhanced level of isolation and security it provides. This benefit is primarily due to the sandboxing nature of Docker containers. In a sandboxed environment, the application (in this case, VSCode) operates in a separate space, isolated from the host system. This isolation plays a crucial role in enhancing security, especially when dealing with untrusted extensions or experimental code.

In a typical setup without Docker, extensions installed in VSCode have access to the host system, which can pose security risks, especially if the extensions are not from trusted sources. In contrast, with Dockerized VSCode environments, VSCode instances only have access to the directories explicitly mounted as volumes in the Docker container. This design means that you, as the user, have full control over which folders the VSCode instance and its extensions can access.

A key security advantage here is that any operation, installation, or changes made within the Dockerized VSCode environment stay within the container. These changes do not affect the host machine directly. For instance, if you're experimenting with a new VSCode extension and it behaves unexpectedly or maliciously, its impact is confined within the container. The host system remains unaffected, safeguarding your primary development setup.

This containment offers a safe playground for testing and using extensions or running experimental code. If something goes wrong, the effects are limited to the container, which can be easily stopped, removed, or reset. Essentially, Docker acts as a robust barrier, ensuring that your host system's integrity and security are maintained, regardless of the activities within the VSCode container.

Furthermore, this isolation ensures that your development environment remains clean and uncluttered. Since nothing inside the Docker container is installed on the host machine, it avoids potential conflicts or pollution of the host's environment. This separation is particularly beneficial for maintaining a stable and secure development environment, especially in scenarios where multiple projects or development contexts are in play.

In summary, the isolation and security provided by Dockerized VSCode environments represent a significant step forward in safe and efficient software development practices. It allows developers to explore, experiment, and work on various projects with the assurance that their primary system remains secure and unaffected.

Resource efficiency

When we compare Docker to traditional virtual machines (VMs), Docker's superior resource efficiency becomes evident. This difference primarily stems from Docker's architecture, which contrasts starkly with that of VMs. Docker containers share the host system's kernel and only require the application and its dependencies to operate. In contrast, VMs need a full operating system to function. This architectural distinction makes Docker containers inherently less resource-intensive, allowing for more agile and responsive environments that are well-suited to modern software development needs.

Docker's lightweight nature is one of its key advantages. Containers require significantly less disk space and memory than VMs, leading to faster deployment and startup times. This quick start-up is especially beneficial in development processes, where time efficiency is critical.

In terms of resource consumption, Docker also has the upper hand. VMs typically allocate fixed resources to each instance, which can be excessive and lead to inefficient resource use. Docker's dynamic resource allocation ensures that containers use only what is necessary, optimizing system resource utilization and enabling the concurrent running of more applications.

Furthermore, Docker simplifies maintenance compared to VMs. A VM is an entire operating system that necessitates regular updates and management, whereas Docker containers, encapsulating only the necessary application components, are easier to manage and update. This simplicity extends to security patches and configuration management.

The most notable advantage, perhaps, is the reduced overhead of Docker containers. By forgoing the need to run a full guest operating system, Docker facilitates better hardware utilization. This efficiency is crucial in environments where resource constraints are a concern, allowing for more applications to be run on the same hardware.

In summary, Docker's containerization approach significantly lessens the strain on computing resources. It provides isolated, efficient, and manageable environments, making it an ideal tool for contemporary development practices that require agility and the ability to quickly adapt to new requirements. Docker's responsive and efficient environment enables developers to focus on innovation and development, unburdened by the complexities of managing weighty and cumbersome VMs.

Consistency, Portability, Version Control, and Update Management

One of the key strengths of using Dockerized environments, especially for development purposes, lies in the consistency and portability it offers across different machines. This aspect is crucial for development teams and individual developers who need to ensure that their applications behave the same way, irrespective of where they are being run.

Docker containers encapsulate not just the application but also its environment. This means that everything needed to run the application - from the operating system, libraries, and dependencies - is packaged together. This packaging ensures that the application runs in the same environment, regardless of the host system. Whether it's being developed on a local machine in Ubuntu or tested on a remote server running CentOS, the application's behavior remains consistent. This consistency eliminates the "it works on my machine" problem, a common challenge in software development.

The containerized approach of Docker also enhances portability. Containers can be easily moved from one environment to another—be it from a local development machine to a test server, or from a staging environment to production. This portability is particularly beneficial for quickly deploying applications or scaling them across different environments or cloud providers.

The practice of version-controlling Dockerfiles and associated scripts (like setup and teardown scripts) plays a pivotal role in maintaining the integrity and history of development environments. By keeping these files in a version control system, teams can track changes over time, easily roll back to previous versions if necessary, and maintain a historical record of how environments have evolved. This practice is particularly useful when parts of the toolchain are updated or when introducing new dependencies.

Having version-controlled Dockerfiles and scripts also simplifies the process of replicating environments. New team members or collaborators can quickly set up their development environments by pulling the latest versions of these files from the repository, ensuring they're working with the most up-to-date configurations.

Furthermore, the ability to version control these environment configurations aids in update management. When a new version of a tool or library is released, it can be tested in isolation, and the changes can be committed only once they are verified to work as expected. This approach minimizes disruptions and ensures that updates are systematically managed.

In conclusion, Docker's ability to provide consistent, portable, and easily replicable environments, combined with the best practices of version controlling Dockerfiles and scripts, makes it an invaluable tool in modern software development. It not only streamlines the development process but also enhances collaboration and reduces the risks associated with environment inconsistencies and updates.

Educational tool

Dockerized environments, particularly when integrated with tools like Visual Studio Code (VSCode), serve as an exceptional educational tool, offering a myriad of benefits for both teaching and learning in the realm of software development. The adoption of Docker in educational settings aligns seamlessly with the growing emphasis on hands-on, practical experience in learning.

The foremost advantage of Docker in an educational context is the creation of a consistent learning environment. This consistency ensures that all students, regardless of their individual hardware or software configurations, have access to the same development setup. Such uniformity is vital in educational settings to avoid discrepancies that can lead to confusion and hinder the learning process.

Moreover, Docker significantly eases the setup of development environments. Educators can distribute pre-configured Docker images or Dockerfiles, enabling students to swiftly and effortlessly establish their development environment. This streamlined setup is especially beneficial in time-constrained educational scenarios like workshops or short courses.

A key aspect of learning with Docker is the provision of a safe, sandboxed space for experimentation. Students can freely experiment with code and various technologies within the confines of Docker containers, without the risk of causing issues in their host system. This safety aspect encourages exploration and learning through experimentation, fostering an environment where trial and error become an integral part of the educational journey.

In addition to facilitating individual learning, Docker’s replicability and scalability are invaluable assets in classroom settings. The ability to deploy the same environment across numerous machines ensures that Docker-based learning can be scaled to accommodate a large number of students, a feature particularly useful in institutional settings.

Resource efficiency is another critical benefit of Docker in education. Its lightweight nature, compared to traditional virtual machines, makes Docker a practical choice for educational institutions that may have limited hardware resources. This efficiency ensures that Docker can be widely adopted without significant hardware investments.

Importantly, learning in Dockerized environments equips students with real-world skills that are highly sought after in the current job market. The practical experience gained with Docker and containerization prepares students for professional roles in software development, bridging the gap between academic learning and industry requirements.

Lastly, the integration of Docker with version control systems in educational projects promotes collaboration and teaches essential skills for modern software development. Students learn about managing collaborative projects and version-controlling Dockerfiles, skills that are indispensable in professional software development teams.

In essence, Docker's adaptability and relevance to industry practices make it an invaluable educational tool, providing a platform that supports both the learning of programming and development concepts and the preparation for the technological demands of the professional world.

You Almost Convinced Me, But I Don't Have Linux on My Machine

If the idea of Dockerized VSCode environments appeals to you but the absence of Linux on your machine seems like a barrier, there's a viable workaround. This involves using a virtual machine to run a Linux distribution, such as Ubuntu, on your current operating system, whether it's Windows or macOS.

The first step in this process is to install VirtualBox, a popular virtualization software. VirtualBox allows you to create a virtual environment on your existing system where you can install and run a different operating system. The process of installing VirtualBox is quite straightforward, with plenty of available tutorials to guide you through the installation.

Once you have VirtualBox installed, the next step is setting up Ubuntu as a virtual machine. This typically involves downloading the Ubuntu ISO file and using it to create a new VM in VirtualBox. The internet is replete with step-by-step guides that walk you through installing Ubuntu on VirtualBox, making it an achievable task even for those new to virtualization.

After successfully installing Ubuntu on your VM, ensure you perform a system update and install Docker and Git. These installations are crucial as they lay the groundwork for setting up the Dockerized VSCode environment. With Docker and Git installed, you can then proceed to follow the instructions provided in this article to create your Dockerized development environment.

One of the great advantages of this approach is that, despite using a VM, you can still benefit from multiple, isolated development environments. Each Docker container within your Ubuntu VM can be configured as a separate development space, complete with its own tools, settings, and dependencies. This means within a single VM, you can simultaneously run multiple development environments tailored for different projects or programming languages.

In essence, this method opens up the possibility of leveraging the benefits of Dockerized VSCode environments, even for those who don't have a Linux-based system. It's a practical solution that extends the power and versatility of Dockerized environments to a wider audience, ensuring that more developers can optimize their workflows and embrace a more efficient development process.

Related Projects

After searching antoher Dockerized VSCode environments browsing internet, several related projects offer various approaches and configurations that could be an alternative to the aproach described in this article:

colebrumley/docker-vscode: Tailored for macOS users, this project provides a Docker container specifically for VSCode. It focuses on a basic setup of VSCode within Docker, making it a straightforward option for those using macOS. GitHub Link.

beatthat/vscodify-docker: This project aims to add a VSCode development environment to any Docker base image. Its flexibility allows integration with a range of Docker base images, offering a generalized solution adaptable to various use cases. GitHub Link.

allamand/docker-vscode: Designed for developers working with Go, this project integrates Go and VSCode in a Docker environment. It's a more focused solution for those specifically developing in Go. GitHub Link.

cmiles74/docker-vscode: Offering a comprehensive development environment, this project includes VSCode along with tools like Dotnet CLI, NPM, and Emacs. It's ideal for developers needing a broad set of resources in their Dockerized VSCode setup. GitHub Link.

Each of these projects presents a unique take on Dockerizing VSCode, ranging from minimal setups to comprehensive environments with multiple tools, highlighting the versatility and adaptability of Docker in development settings.

Implementing Dockerized VSCode Environments: A Detailed Guide

In this chapter, we delve into the practical aspects of setting up your Dockerized Visual Studio Code environment. This step-by-step guide will walk you through the process using Dockerfile configurations and scripts from the GitHub repository, complete with code snippets and explanations. The focus will be on understanding the Dockerfile content and the run.sh script, which are crucial to deploying the VSCode environment within Docker.

  • Explaining the Dockerfile: We'll break down the Dockerfile line by line. This includes understanding the base image selection, the installation of dependencies, the addition of the Microsoft repository for VSCode, and the creation of a non-root user. Each step will be explained to provide insight into why these elements are essential for a stable and functional Dockerized VSCode environment.

  • Understanding the run.sh Script: The run.sh script is key to initiating and running the Dockerized VSCode. We will explore the script's functionality, including setting up X11 forwarding for GUI support, configuring Docker run commands, and mounting necessary volumes. This explanation will help you understand how the script interacts with the Docker environment to launch VSCode.

  • Code Snippets and Execution: Actual code snippets from the Dockerfile and run.sh script will be presented. These snippets will be accompanied by instructions on how to execute them, ensuring you can follow along and implement the setup in your own environment.

  • Customization Tips: Additional tips on how to customize the Dockerfile and the script for various development needs will be provided. This will enable you to tailor the Dockerized VSCode environment to fit your specific project requirements.

Explaining the Dockerfile

# Use Ubuntu Jammy as the base image
FROM ubuntu:jammy-20240111

# Update package list and install dependencies
RUN apt-get update && apt-get install -y \
    wget \
    gnupg \
    software-properties-common \
    apt-transport-https \
    git \
    iputils-ping \
    iproute2 \
    && rm -rf /var/lib/apt/lists/*

# Add the Microsoft repository for VSCode
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg \
    && install -o root -g root -m 644 packages.microsoft.gpg /usr/share/keyrings/ \
    && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list

# Install Visual Studio Code
RUN apt-get update && apt-get install -y code

# Create a non-root user
RUN useradd -m vscodeuser
USER vscodeuser

# The command to run VSCode
CMD ["code", "--no-sandbox", "--wait", "--verbose"]
Enter fullscreen mode Exit fullscreen mode

The Dockerfile starts by specifying Ubuntu Jammy (version 22.04 as of 2024) as the base image. This choice is due to Ubuntu's stability and popularity, making it an ideal choice for a consistent development environment.

The next steps involve updating the package list and installing essential dependencies. These include tools like wget for downloading files, gnupg for encryption, git for version control, iputils-ping and iproute2 for being able to execute some network related commands in visual studio console like ping or ip address, among others. This setup ensures that all necessary tools I considered are available in the Docker environment.

To install VSCode, the Dockerfile adds the Microsoft repository. This is done by downloading Microsoft's GPG keys, ensuring the authenticity of the packages, and then adding the VSCode repository to the sources list. With the repository in place, the Dockerfile proceeds to update the package list again and installs VSCode. This ensures that the latest version of VSCode when generating the image is installed in the Docker container.

The Dockerfile also takes care of creating a non-root user, vscodeuser. Running applications as a non-root user in Docker containers is a good practice for security. This user will be related to the user that runs the script on the next section (more on this later).

Finally, the Dockerfile specifies the command to run VSCode (code) with certain flags for compatibility and verbose logging. We will run docker with -d flag so logging will not be visible when running docker, but to access logs, docker logs command will be needed to run whilst the container is running. I was not able to run vscode without adding --no-sandbox, but I do not think this is important for this case as we are already controlling how vscode will be sandboxed. Finally, --wait was also needed, because otherwise the container would be killed as soon as vscode is launched.

Understanding the run.sh Script

#!/bin/bash
XAUTH=${XAUTHORITY:-$HOME/.Xauthority}
DOCKER_XAUTHORITY=${XAUTH}.docker
cp --preserve=all $XAUTH $DOCKER_XAUTHORITY
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $DOCKER_XAUTHORITY nmerge -

docker run --rm -d \
    --name vscode-base-1 \
    -u $(id -u):$(id -g) \
    -v /etc/passwd:/etc/passwd:ro \
    -e DISPLAY=unix$DISPLAY \
    -e NO_AT_BRIDGE=1 \
    -e GTK_THEME=Adwaita \
    -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
    -e XAUTHORITY=$DOCKER_XAUTHORITY \
    -v $DOCKER_XAUTHORITY:$DOCKER_XAUTHORITY:ro \
    -v ./home:/home/vscodeuser \
    -v ./Documents:/home/vscodeuser/Documents \
    -v ./home:/home/$USER \
    -v ./Documents:/home/$USER/Documents \
    -e HOME=/home/vscodeuser \
    inigoetxaniz/vscode-base:latest 
Enter fullscreen mode Exit fullscreen mode

The run.sh script is crucial for launching the Dockerized VSCode environment. It handles several important tasks that are not so habitual when working with docker.

Perhaps, one of the most unused features of docker is forwarding X11 for GUI support. X11 is a protocol for remote graphical interfaces. It's commonly used to display GUI applications running on a server onto a client machine. In the context of Docker, X11 forwarding enables GUI applications running inside a Docker container, like VSCode, to display on the host machine's screen. The script sets up X11 forwarding by first determining the X11 authentication file location and then copying this information into a new file (DOCKER_XAUTHORITY). This step ensures that the Docker container has the necessary permissions to display its GUI on the host's screen.

Later, the docker run command includes several options to facilitate X11 forwarding. -e DISPLAY=unix$DISPLAY sets the DISPLAY environment variable inside the container, pointing it to the host's display. -v /tmp/.X11-unix:/tmp/.X11-unix:ro mounts the X11 Unix socket from the host into the container, allowing the container to communicate with the host's X server. -e XAUTHORITY=$DOCKER_XAUTHORITY and the associated volumen mount -v $DOCKER_XAUTHORITY:$DOCKER_XAUTHORITY:ro pass the authentication details to the container. This setup works well in an Ubuntu environment, but it's important to note that X11 forwarding configurations may vary based on the host's operating system and Docker version.

While X11 forwarding in Docker is an effective way to display GUI applications like VSCode from a container to the host screen, it's important to consider security implications. X11 forwarding allows the container to interact with the host's X server, which could potentially be a security concern. However, in the context of running a trusted application like VSCode, this setup does not introduce significant additional risks compared to running VSCode directly on the host.

When using X11 forwarding to run VSCode in Docker, the security risk is comparable to running it natively on your system. The isolation provided by Docker still offers a layer of protection, as the activities within the container remain separate from the host system. This means that while the container can display its output on the host, it doesn't have unrestricted access to the host system.

In essence, using X11 forwarding for trusted applications like VSCode in Docker strikes a balance between functionality and security. It allows for the convenience and benefits of Dockerized environments without adding significant security threats, especially when used in trusted and controlled development scenarios.

In this case vscode-base-1 is the name of the container. --rm and -d options make docker container to be deleted after vscode is exited and also run in a detached mode so that nothing is shown in the console and application returns to the user. Logs are still available running in the console docker logs vscode-base-1. -v ./home:/home/vscodeuser sandboxes all home related files to ./home, which allows us to store configuration options, extensions installed during ussage of vscode and other things we could install during the use. -u $(id -u):$(id -g) sets running user as current user in the host. -v ./Documents:/home/vscodeuser/Documents allows us to control which content is accessible from vscode and this is where I propose to store your working project files. Finally, -e HOME=/home/vscodeuser sets /home/vscodeuser as home folder independently of the current user name that is launching vscode from docker. I also added -v ./home:/home/$USER and -v ./Documents:/home/$USER/Documents, because in some cases docker container was refering to the user name that launched docker container. Now both /home/vscodeuser and /home/$USER point to the same location solving this issue.

This guide to setting up a Dockerized VSCode environment, particularly the run.sh script, exemplifies the balance between sophisticated functionality and user-friendliness. The script simplifies complex processes like X11 forwarding, making it accessible even to those who might be new to Docker or Linux environments. By automating intricate setup steps and providing clear explanations for each part, it significantly eases the user's journey in creating a secure, isolated, and efficient development workspace. This approach not only caters to advanced users seeking customization but also to beginners desiring a straightforward path to leveraging Docker for their development needs.

Use Cases and Customization

This section introduces two practical use cases, illustrating how you can tailor Dockerized VSCode environments for specific development needs. The first example focuses on creating an environment for Go language development, while the second caters to developing VSCode extensions with Node.js and NPM. These examples are not exhaustive but serve as a starting point for customization.

Both Dockerfiles begin with the inigoetxaniz/vscode-base image, ensuring a consistent foundation. Customizations involve switching to the root user to install necessary packages specific to each environment — Go for the first and Node.js for the second — and then reverting back to the non-root vscodeuser. This approach maintains security while allowing for necessary installations.

Environment variables are set up as needed, particularly for the Go development environment, to ensure the correct configuration of the workspace. The run.sh scripts for these environments are similar to the one detailed earlier, underscoring the ease of adapting the base setup to various requirements.

These Dockerfiles demonstrate the flexibility and adaptability of Dockerized environments, providing a blueprint for developers to create their customized setups. Whether it's for a specific programming language or a particular type of development work, these examples show how Docker can be effectively used to create efficient and tailored development environments.

# Start from the base image you created for VSCode
FROM inigoetxaniz/vscode-base:latest

USER root

# Download and Install Go
RUN wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz \
    && tar -C /usr/local -xzf go1.21.6.linux-amd64.tar.gz \
    && rm go1.21.6.linux-amd64.tar.gz

USER vscodeuser

# Set up the Go environment
ENV GOPATH /home/vscodeuser/go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH

# The command to run VSCode
CMD ["code", "--no-sandbox", "--wait", "--verbose"]
Enter fullscreen mode Exit fullscreen mode
# Start from the base image you created for VSCode
FROM inigoetxaniz/vscode-base

USER root

# Download and install the latest LTS version of Node.js
RUN apt update && \
    wget https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-x64.tar.xz && \
    tar -xJf node-v20.11.0-linux-x64.tar.xz -C /usr/local --strip-components=1 && \
    rm node-v20.11.0-linux-x64.tar.xz

RUN npm install -g yo generator-code

# Switch back to the non-root user
USER vscodeuser

# The command to run VSCode
CMD ["code", "--no-sandbox", "--wait", "--verbose"]
Enter fullscreen mode Exit fullscreen mode

Two different instances of vscode in same machine

Conclusion

In this guide, we've explored the versatile and secure world of Dockerized VSCode environments. Key highlights include the use of Docker for creating isolated, customizable, and resource-efficient development spaces, enhancing both security and consistency across various machines. We've dissected the Dockerfile to understand its structure and purpose and delved into the run.sh script, particularly focusing on X11 forwarding for GUI support in Docker. We've also covered practical use cases, demonstrating how to tailor Dockerized environments for specific development needs like Go and Node.js. This guide not only provides a step-by-step process for setting up these environments but also emphasizes their adaptability and ease of use, making Dockerized VSCode a valuable tool for developers seeking an efficient and secure development workflow.

Top comments (0)