If you stumbeled over this post, you probably already heard of know solutions like Gitpod, Github Codespaces or Coder to quickly spin up a development environment on the go. While I was using them I got fascinated about their capabilites and wished that I could use them for everything I do. Unfortunately some of these solutions are tied to a specific platform and or repository or they don´t offer you the performance or control that you would like. That was when I stumbeled over code-server, a web-based build of Code OSS. That was the missing piece I needed to finally build my own codespace-like solution, but that time with full control.
So this post documents how I setup a server for remote coding usage. In case you’re more after a cloud-based solution with ephemeral reproducable machines (that are otherwise setup more or less the same as here), I suggest you checkout tevbox, where I did exactly this with a lot of automation.
The server
Well I’m not using a server, but an old desktop I had lying around. Phyiscal hardware is cumbersome to manage but for a static thing like a code-server it works fairly well I think.
The specs:
Part
Description
Desktop
HP EliteDesk 800 G1 SFF
CPU
Intel(R) Core(TM) i5-4570 CPU @ 3.20GHz
Memory
32GB DDR3 (overkill, but I had it lying around)
Disk
120GB SSD for OS, 500GB SSD for Data (not formated/mounted but one day I’m glad I have it)
NIC
just some dump 1Gbit/s NIC
The Operating System
I installed Ubuntu Server 22.04.3 LTS on the main disk using LMV to partition the disk automatically. Ubuntu Server is simple, widely adopted, fairly minimal and thus perfect for that kind of server. The manual install has to be done only once and LVM gives you the flexibility to tweak the disk-partitioning later on in case you want that.
Networking
Ubuntu comes with netplan by default, but I don’t like it, especially if systemd already has a preinstalled solution. So I purged netplan and replaced it with systemd-networkd:
This works even with active VPN / SSH connections btw ;).
Tailscale
I always install Tailscale on my systems so that as long as they have egress connectivity I can connect to them somehow. Tailscale also has a feature called Funnel that allows you to expose service in the internet without a public IP or open ports. We will use this later on.
To install Tailscale, there’s a oneliner: curl -fsSL https://tailscale.com/install.sh | sh.
To bring it up, there’s a oneliner too: sudo tailscale up --ssh
UFW
It’s always a good practice to use a host-firewall. I configured ufw for that:
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow in on tailscale0
Unattended-Upgrades
To be secure all the time and since the server will later be exposed to the internet I’ll activate automatic security patches:
Now that the server’s OS is configured, we get to the main part: the coding.
To install code-server run the following:
curl -fsSL https://code-server.dev/install.sh | sh
sudo systemctl enable --now code-server@technat
This installs the code-server as systemd-service and enables it for your current user. The default for code-server is to listen on 127.0.0.1:8080, but I’ll change that to something less common (to avoid conflicts):
sed -ei 's/^bind-addr.*/bind-addr: 127.0.0.1:65000/g' ~/.config/code-server/config.yaml
Don´t forget to restart the service with sudo systemctl restart code-server@technat after that change.
Configuring code on code-server
The Code OSS part of code-server has it’s config in .local/share/code-server/User/settings.json. That’s what you will automatically configured if you change settings in the web UI of code-server. I usually put some defaults in there:
As you have noticed so far, code-server listens on localhost, but we want to use it from everywhere. That’s a sane default code-server uses here because code-server itself isn’t meant to be exposed directly. Instead you should use a reverse-proxy as the docs suggest.
As mentioned earlier on, that’s where Funnel comes into play. My machine is already connected to Tailscale, so I run:
sudo tailscale funnel --bg 65000
Funnel must be allowed for your machine, but when you run the command tailscale will tell you that. It’s also a good practice to read the docs about Funnel to learn about the prerequisites.
Please note that exposing local development services via Tailscale is only possible path-based. I haven’t yet found a better solution to that.
Authentication
Now that code-server is exposed in the internet, we should add some authentication. By default code-server has a builtin password authentication with a randomly generated password. But I don’t like this and replaced it with an OAuth Flow to sign-in with Github.
First thing to do for this is to disable the current authentication:
sed -ei 's/^auth:.*/auth: none/g' ~/.config/code-server/config.yaml
sudo systemctl restart code-server@technat
Be sure to replace your username in --github-user. It’s the only directive that’s currently somehow not supported in the config file.
Once it’s running we can create an OAuth app in Github according to this doc. I’ll use https://my-machine.blabla.ts.net/oauth2/callback as callback URL.
And then we create the config file for oauth2-proxy:
footer="-" cookie_domains=".ts.net" cookie_secure=truecookie_expire="2h"http_address="127.0.0.1:65001"reverse_proxy=true # Are we running behind a reverse proxy? Will not accept headers like X-Real-Ip unless this is set.provider="github"client_id="REPLACE_ME"client_secret="REPLACE_ME"cookie_secret="$(openssl rand -base64 32 | tr -- '+/' '-_')" # generate new cookie secret with this commandemail_domains=["*"] upstreams=["http://127.0.0.1:65000/" ]
Some notes:
footer disables the version to be shown on the sign-in page
cookie_secret run the command to generate your unique cookie secret
--github-user the config allows everyone with a Github account to sign in, somehow the --github-user flag can’t be translated into a config directive, but as shown and mentioned before this flag is set on the systemd service.
Restart the service after you created the config file. Finally we can recreate our funnel to point to auth2-proxy instead of code-server:
Now that you have your code-server you might want to activate extensions and install progamming languages. I’ll skip this topic as it highly depends on what you are using your code-serer for. I will just run: sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply the-technat and I’m done with it.