Cobalt: A new blog engine for a new decade
Originally this blog was created on Blogger and hosted on Blogspot. Then in 2011 I migrated to Jekyll and hosted it on GitHub Pages. Now it's built using Cobalt and hosted on Netlify.
Each of these platforms are static site generators. Even Blogger supported publishing the generated file over FTP back in the day! Static site generators combine a nice developer experience of using templates and a great deployment model where you just host files on a website.
This time the move is motivated by frustration Jekyll and the developer experience of Ruby. It's not that Ruby has gotten worse, it's my expectations for installing and running software has changed.
The script software deployment model
When platforms like Python, Ruby, Java, and .NET were created in the 1990s, they all had a runtime that was installed once for the entire system. Since bandwidth was limited, applications could be smaller to download by depending on the installed runtime. Linux distribution maintainers could make a single coherent set of packages containing all the libraries and applications. As long as the software was in the distribution's software repository, users did not have worry about compatibility.
Over time languages like Perl, Python, and Ruby developed their own software package ecosystems and their own software package managers. Initially these package managers installed packages globally 1. This could cause DLL Hell style problems, where different applications would depend on different versions of the same package. Updating the packages for own program could break another.
The Ruby and Python ecosystems responded by creating tools like Ruby Version Manager and Virtualenv. These tools isolate installs of the runtime and packages from other parts of the system. These do work, but you have to make sure you always have your environment setup right for each project. And the source-based distribution model for Python and Ruby means you are constantly downloading the same packages and rebuilding the same binary dependencies.
This source-based distribution model play especially badly with modern Continuous Integration systems. These systems often download and build the dependencies every time the software is built2. For this website, the system would download and build Jekyll every time I deployed. It could take 5 minutes to generate a handful of HTML files.
Meet the new software deployment model, same as the old model
Rust and Go throw away this source based distribution complexity and go back to the old style of compiling the application ahead of time. Then you distribute and run those binaries. This is to the model that C programs use. But Rust and Go come with build tools that make it much easier to build software for multiple different operating systems and architectures.
Both Rust and Go have built in package managers. Rust in particular3 keeps all your projects separate so packages in one project don't interfere with other projects.
This is really the killer feature4 of Rust and Go programs: you can be confident when you download a binary it just going to work. Deployment on a server or container becomes trivial: download the executable and run.
This is similar to benefits of static site generators: the development and build processes are cleanly separated from the deployment process. Once built, the website can deployed and run without any of the development environment.
.NET had the Global Assembly Cache, it support multiple versions of the same assembly side by side. However it was such a hassle to use that it never really caught on.
This can be worked around by building custom docker images with the dependencies already install and the application source code mounted in. But that's annoying to maintain.
I'm not super up to date with Go, but my impression was that
as kind of a global install folder. But many applications vendor all their
Also in fairness, .NET Core's Single File Applications and Graal's native-image for Java also have pretty nice deployment characteristics. They produce 10MB ~ 15MB binaries for hello world. While that's still a bit fat, it's not too far from the 1MB ~ 2MB hello world binaries from Rust and Go.