Easily developing webservices locally in a production like environment
28 May 2020
One thing that’s always frustrated me about web development is how much as a community we rely on sqlite as our DB for local development and testing. Sure it’s convenient, but it also has very different semantics from a fully fledged database service (Postgres, MS SQL, etc.) that it makes me nervous asserting any sort of behaviour correctness from its use. But is sure is convenient compared to standing up a full DB, especially if you have to work on multiple projects each with their own DB requirements.
Part of the reason we shy away from standing up our own DB server is they’re not trivial to setup, and if you don’t want them running all the time it’s tedious having to get everything up and down again each time you need it. That’s assuming the DB is available on the platform you use for development (e.g., you work on a Mac but need MS SQL).
The rise in popularity of container based deployment for web apps certainly makes a lot of this easier: it is now a lot easier to stand up a DB with a given configuration on your machine, and even takes care of some of the compatibility cases (you can run the MS SQL docker container on the Mac for instance). But you still need to remember to set up those containers before you work, and it’s frustrating to have your battery run out on your laptop whilst out and about because you forgot to tear down your test DB before you left the house.
This is also just one symptom of a broader problem: modern web services don’t just talk to a DB: they sit behind a load balancer, they talk to other storage systems like redis or memcache, they send logs to fluentd or logstash. If you’re developing locally or running tests shouldn’t you be testing against all these bits too?
Thankfully I’ve found a way, that for me at least, solves this rather neatly.
Last week I virtually attended Microsoft’s Build developer conference, and I happened across a solution that seems to solve this quandry, at least for the two main client projects I work on regularly. Microsoft didn’t highlight this particular solution directly, I just spotted it thanks to being the infrastructure from which one of their new headline features was built upon: the Visual Studio Codespaces, which is an extension of their free development editor Visual Studio Code to let people hack on github projects magically without needing a local dev environment set up (it’s cool, and you should check it out also).
Let’s start with Visual Studio Code: it’s Microsoft’s free open source code editor, which I’ve taken to using since leaving the Mac as my main primary development platform (before I used BBEdit). Because I now flit between using Windows 10, Linux, and macOS, depending on the project, I wanted a single editor that would run everywhere. However, when I made this choice I’d not realised that one feature it has that other editors dont’ would become a key thing I use daily: it’s ability to run not just anywhere, but in multiple-anywheres thanks to its remote development feature.
Basically whilst you run the frontend (the display bit) of VS Code locally, the backend (the bit that manages your files, does syntax highlighting, and even runs the code and debugs it) can be run not just on your local machine, but on another computer entirely:
I use this almost daily when using the Windows Subsystem for Linux, which is how I’ve ended up switching to Windows 10 as my primary platform. I do all my dev work in the Linux environment, but get the benefits of Windows working better on my laptop than Linux does itself. I also use this to let me do quick code changes to Xcode projects by having VS Code ssh into my Mac and let me edit Swift files there and I can build using command line tools (this works until I need to change the project or edit storyboards, so it’s not a huge benefit, but is a time saver when I can get away with it).
So, I’m sure most of you can see that this remoting can work with a set of docker containers: you can start up a docker environment, remote into it to do your dev work, and then tidy up afterwards. But it turns out VS Code will go one better than that: it knows how to manage the docker containers itself (with a little help from you to point it at the correct docker files).
In VS Code you can set up a devcontainers configuration, and it’ll automatically fire up a container when you open your project and let you work there as if it was native: no need for you to remember to launch the container and to shut it down afterwards. It’ll also mount your code directory so that the edits you do there will be on your local machine to, so no need to worry about your work being somewhere else.
Microsoft provide some templates for most common languages out there for you to try, so you can open your Python, Go, Node project in a container without having to have that toolchain installed locally. Their Go one is really neat, with breakpoints and other interactive features just working out the box.
All the templates just use a single container managed from a Dockerfile, but if you look through the feature documentation you’ll find it also supports docker-compose configuration, so you can bring up not just a container for your bit of code, you can also bring up all the surrounding infrastructure: a database container, a redis container, a fluentd container etc. Indeed, I’m now using a full Elastic Search/Kibana stack for looking at my local debug output, which is quite neat compared to digging through logs. And the best bit is VS Code will manage creating all this and tidying it all away when I’m done - and for this my battery life is grateful.
The docker-compose path isn’t quite as easy to setup as you’d hope. Firstly, in production most projects will not use a container for their service that has anything like a full interactive environment, as these are large to move around and represent a security risk, so are using instead either a minimal Alpine linux stack or, if you’re using Go, even a bare scratch image. Thus you’ll want to make a custom development Dockerfile for the service you’re working on, and for that I’m just taking the Microsoft example container for that particular language (Python, Go, etc.) and tweaking that.
This in turn means you need to tweak your docker-compose file. Normally you’d use an override file to just swap out a production container for a development one, but unfortunately, as far as I can tell, the VS Code dev container configuration file only lets you specify a full docker compose file, so I had to duplicate my production one (and I’ll need to remember to keep the two files in sync over time).
The third bit of friction to getting this set up is you only get to see stdout/stderr from the container that you nominate as your development environment, so if you have failures in your other containers you won’t see them. This is generally not a problem once you have things set up and working, but in that initial bootstrapping period can be quite frustrating. If you have a EFK/ELK logging stack then I’d recommend getting all your containers to log to there, or making everything log to a shared volume that is mounted in your development container also to make all the logs easier to inspect.
The final thing to note here is that this does seem to have a bit of a performance hit compared with running locally, at least on my older laptop. But I’m more interested in my manual and automated testing being correct due to hitting the same environment as production vs being fast but using an approximation of a database rather than the real thing.
So, there is some friction to get set up, but once you get through that initial phase it does just work. Now I’m now able to develop and test my code as if I was in the cloud itself (which, if you read up on the Codespaces feature I mentioned above, is where Microsoft are going with this), and I never realise half a day later I’m still running postgres and that’s why my Adobe tools are so slow.
I spotted today this sort of container orchestration is coming to the full Visual Studio, which is neat, but until Visual Studio works with Python/Django or Go less useful for the projects I work on, and VS Code lets me do this today.