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
- A Linux server or VM (I’m using Ubuntu 18.04)
- 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:
- Install NodeJS, the FiraCode font, and docker-compose via Python (I know you can just download the executable, but I want Python installed anyway)
- Add the
coder
user to thedocker
group for non-root access to Docker on thecode-server
container (You’ll have to restart the container for this to take effect) - Configure git
- 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) - 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:
- Clone any Dockerized project into the
~/git
directory from VSCode’s terminal, open and start the project (e.g.docker-compose up
). - Access the project in development from it’s respective port on the host machine (e.g. 3000, 8000, etc.)
- That’s it, now you can get some work done!
Recommended Extensions
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:
- 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.
- Prettier - Code Formatter (esbenp): Code formatter, don’t forget to enable the “Format on save” option.
- GitLens - Git supercharged (eamodio): Git history explorer.
- One Dark Pro (zhuangtongfa): My theme of choice, the dark theme for dlford.io was heavily inspired by One Dark Pro.
- vscode-icons (vscode-icons-team): This should just be added to VSCode by default in my opinion.
- Bracket Pair Colorizer (CoenraadS): This one too.
Tips
- You can create the files
update-portainer.sh
andupdate-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 runsudo chmod +x /usr/local/bin/update-*.sh
. Now you can just runupdate-portainer.sh
orupdate-vscode.sh
from the host machine to run those updates with a single command.