Skip to main content

Setting up a Remote Docker VSCode Development Server

Pulblished:

Updated:

Comments: counting...

In this article you will learn how I set up a remote development server for cloud-native Docker apps.

The Why

I recently picked up a cheap 10.1" ARM Chromebook to try out as a light-weight mobile development machine (an Asus C101PA to be specific). I had hoped for a very portable machine that I could develop on with long battery life thanks to ARM, at the cost of some extre build time that I was willing to accept.

The C101PA is a great machine that checked almost all the boxes, it’s a joy to carry around and pretty usable to work on given it’s small size, and the battery life is stellar. The trouble is I can’t actually build most of my projects due to some compatability issues with ARM.

In addition to that, Crostini (ChromeOS’ Linux Container) is still in beta and it shows, I spent weeks trying to assemble a usable development environment, I think I was pretty close so I tried to run a backup that failed, and I lost it all. I couldn’t even restore from a previous backup!

I think it could work one day, it just needs some more time to fully cook, in the meantime I’ve set up a remote development environment that I can use from anywhere (even a Chromebook!), and I’m so pleased with it that I’m actually writing this article on my remote dev server from my desktop workstation, in lieu of the VSCodium installation on my local machine.

Pros

  • Compatibility: My main motivation for trying it at all, if your device has a modern web browser, you can get work done.
  • Persistence: It’s so freeing to be able to just shut down your workstation and walk away knowing you can log in to the web interface from any other machine and have everything exactly as you left it.
  • Correlation: No more hunting for that VSCode extension or some other software that you remember using on another machine but forgot what it was called, or keeping a config.txt file so you know how to set up a new machine down the road, there is only one development environment to maintain, and backups are a breeze.
  • Performance and Endurance: When working remotely on a laptop, all the heavy lifting is done server-side, so you spend less time waiting and get more time between charges, a total win-win.

Cons

  • Connectivity: Obviously, if you can’t access it you can’t use it, but I have tested it with a simulated poor network connection on Chrome, and the experience is actually quite good once the initial page has loaded.
  • Dependence: If you stop maintaining local development environments and something happens to the server, you’ll have some extra work to do before you can be productive. Also, I’m generally a Firefox guy, but code-server works better on Chrome, so that’s another small con in my book.
  • Unknown: I’m not sure what else could go wrong, but I’m going all in so we’ll see what happens!

Requirements

  1. A Linux server or VM (I’m using Ubuntu 18.04)
  2. A cloud-native workflow: I dockerize my apps in development and push pre-built production images to a Docker registry for deployment, if you haven’t tried this workflow I really think you should! (I have an article in the works on this topic, so keep an eye out for that if you’re interested)

Setup

Important Note: This configuration is not very secure, I made a lot of choices that put developer experience over security, so please keep it far away from your production environment, and I don’t recommend exposing this system to the internet, I use a VPN for access instead.

Docker

First things first, we need to get Docker up and running.

Steps taken from docs.docker.com

sudo apt install -y apt-transport-https \
  ca-certificates curl gnupg-agent \
  software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo apt-key add -

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt update

sudo apt install docker-ce docker-ce-cli containerd.io

Let’s add your user to the docker group so you can use Docker as a non-root user.

sudo usermod -aG docker YOURUSERNAME

Portainer

Who doesn’t appreciate a nice GUI from time to time? I like to keep Portainer on port 81, and VSCode on port 80, feel free to change it up if you disagree.

These commands will get Portainer running with all the bells and whistles:

docker pull portainer/portainer

docker volume create portainer_data

docker run -d --name portainer_gui \
  --restart always \
  -e "CAP_HOST_MANAGEMENT=1" \
  -p 81:9000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  -v /:/host \
  portainer/portainer

To update Portainer, just remove it and spin it up again after fetching the latest image (and skip creating the volume since it already exists), your configuration will persist on the portainer_data volume and be re-attached to the new container:

docker stop portainer_gui

docker rm portainer_gui

docker pull portainer/portainer

docker run -d --name portainer_gui \
  --restart always \
  -e "CAP_HOST_MANAGEMENT=1" \
  -p 81:9000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  -v /:/host \
  portainer/portainer

After setting a username and password under the Users menu at http://IPADDRESS:81, head to Endpoints > local and set the Public IP to the hosts IP address (or hostname if it’s configured with DNS on your network), so when you click the links on published ports in the containers list it opens the correct URL.

VSCode

This parts a bit tricky, what we’re going to do is spin up the codercom/code-server image with access to your home folder on the host, and the hosts Docker instance, so we can spin up containers on the host from the VSCode container. This isn’t quite the intended use case for the code-server image, but with a little tweaking it works pretty well.

First we need to symlink /home/coder to your users home folder to make bind mounts work on project containers, since the path will be in the context of the host and not the VSCode container which uses coder as a username and home folder, we’ll also create a few directories for bind mounts on the container.

The VSCode image uses ~/project as a workspace by default, so it will mount the path automatically if you don’t specify one. I prefer to work out of a git directory, so I just use project to store things pertinent to the VSCode server since it has to be declared. The vscode directory we’ll mount as the home folder in the VSCode container, to save any extensions and settings you’ve configured for VSCode.

sudo ln -s /home/YOURUSERNAME /home/coder

mkdir -p ~/vscode ~/git ~/project

Since I plan to re-create the code_server container every now and then with the latest image, I’ll create an install script in the project folder to configure anything that won’t be stored in the vscode directory (home folder).

touch ~/project/install.sh

chmod +x ~/project/install.sh

nano ~/project/install.sh

Here’s my script as an example, which will:

  1. Install NodeJS, the FiraCode font, and docker-compose via Python (I know you can just download the executable, but I want Python installed anyway)
  2. Add the coder user to the docker group for non-root access to Docker on the code-server container (You’ll have to restart the container for this to take effect)
  3. Configure git
  4. Install Prettier globally via NPM (after installing the Prettier extension in VSCode, add the line "prettier.prettierPath": "/usr/lib/node_modules/prettier/", to your VSCode config)
  5. Take ownership of the git directory and it’s children, which will be owned by the host machine’s user anytime the VSCode container is re-created

Note: Make sure you have the correct group ID for the docker group, in my case it’s 119, you can check by running getent group docker | awk -F: '{printf "The GID for group %s is %d\n", $1, $3}' on the host machine.

#!/bin/bash

curl -sL https://deb.nodesource.com/setup_12.x \
  | sudo -E bash -

sudo apt update

sudo apt install -y \
  nodejs \
  python \
  python-pip \
  python-openssl \
  libssl-dev \
  fonts-firacode

sudo pip install docker-compose

sudo groupadd docker -g 119
sudo usermod -aG docker coder

git config --global user.name "dlford"
git config --global user.email "my@email.address"
git config --global push.default simple
git config --global credential.helper store

sudo npm install --global prettier

sudo chown -R coder. ~/git

Next we’ll spin up the VSCode image, make sure you change the password, and the username in the volume mount paths.

docker pull codercom/code-server

docker run -d --name code_server \
  --restart always \
  -p 80:8080 \
  -e "PASSWORD=replace this text with a secure password!" \
  -v "/usr/bin/docker:/usr/bin/docker" \
  -v "/var/run/docker.sock:/var/run/docker.sock" \
  -v "/home/YOURUSERNAME/vscode:/home/coder" \
  -v "/home/YOURUSERNAME/git:/home/coder/git" \
  -v "/home/YOURUSERNAME/project:/home/coder/project" \
  codercom/code-server

Then run the install script and restart the container to apply the addition of the coder user to the docker group.

docker exec -it code_server /home/coder/project/install.sh

docker stop code_server

docker start code_server

To update VSCode:

docker stop code_server

docker rm code_server

docker pull codercom/code-server

docker run -d --name code_server \
  --restart always \
  -p 80:8080 \
  -e "PASSWORD=replace this text with a secure password!" \
  -v "/usr/bin/docker:/usr/bin/docker" \
  -v "/var/run/docker.sock:/var/run/docker.sock" \
  -v "/home/YOURUSERNAME/vscode:/home/coder" \
  -v "/home/YOURUSERNAME/git:/home/coder/git" \
  -v "/home/YOURUSERNAME/project:/home/coder/project" \
  codercom/code-server

docker exec -it code_server /home/coder/project/install.sh

docker stop code_server

docker start code_server

Usage

You can now access your remote VSCode server from the host machine’s IP address on port 80 (or domain name if you’ve configured it on your network), and Portainer on port 81, using it is pretty straightforward:

  1. Clone any Dockerized project into the ~/git directory from VSCode’s terminal, open and start the project (e.g. docker-compose up).
  2. Access the project in development from it’s respective port on the host machine (e.g. 3000, 8000, etc.)
  3. That’s it, now you can get some work done!

Not all VSCode extensions will work on code-server, but I’ve only run into a couple that don’t so far, here’s a few I’d recommend:

  1. Docker (PeterJausovec): Adds syntax highlighting for Docker related files, and adds a Docker Explorer to the main panel where you can start/stop/attach/view logs for containers on the host.
  2. Prettier - Code Formatter (esbenp): Code formatter, don’t forget to enable the “Format on save” option.
  3. GitLens - Git supercharged (eamodio): Git history explorer.
  4. One Dark Pro (zhuangtongfa): My theme of choice, the dark theme for dlford.io was heavily inspired by One Dark Pro.
  5. vscode-icons (vscode-icons-team): This should just be added to VSCode by default in my opinion.
  6. Bracket Pair Colorizer (CoenraadS): This one too.

Tips

  1. You can create the files update-portainer.sh and update-vscode.sh in /usr/local/bin/ on the host, and copy the respective commands to each file, adding #!/bin/bash to the top, and then run sudo chmod +x /usr/local/bin/update-*.sh. Now you can just run update-portainer.sh or update-vscode.sh from the host machine to run those updates with a single command.