Building a Rust service with Nix
I often give bits and pieces of advice on how to build Rust stuff the comfy way. But it can be hard to see how everything comes together, especially when it comes to, say, deploying a web service in production.
So, let's start from the very beginning (setting up a Linux VM), and march together towards the objective: a production-grade Rust web service, built with Nix.
Series overview
The first step to using Nix to build Rust is to do so without Nix, so that when we finally do, we can feel the difference.
There's many ways to go about this: everyone has their favorite code editor, base Linux distribution (there's even a NixOS distribution, which I won't cover). Some folks like to develop on macOS first, and then build for Linux.
With the previous part's VM still running, let's try connecting to our machine over SSH.
Network addresses, loopback and IP nets
Normally, to connect to a machine, you'd find its IP address. On Linux, a decade
ago, you would've used ifconfig
. Nowadays you can use ip addr
:
The loopback interface (lo
) is local, so it's not useful to reach the box from
the outside: you can see it can be accessed over IPv4 at address 127.0.0.1 but
not just! What we're reading here is 127.0.0.1/8, which corresponds to the range
Now that our development environment is all set up, let's make something useful!
Creating the catscii
crate
From a VS Code window connected to our VM (as we just set up), let's make a new Rust project:
amos@miles:~$ cargo new catscii Created binary (application) `catscii` package
And open it in a new VSCode window:
Our catscii
program does everything we want it to do, except that it's a
command-line application rather than a web server. Let's fix that.
Enter axum
The documentation for the axum crate tells us how to make a basic web server, and we honestly don't need much more than that.
So let's add axum:
amos@miles:~/catscii$ cargo add axum@0.6 Updating crates.io index Adding axum =0.6 to dependencies. Features: + form + http1 + json + matched-path + original-uri + query + tokio + tower-log - __private_docs - headers - http2 - macros - multipart - w
Now that our service is production-ready, it's time to deploy it somewhere.
There's a lot of ways to approach this: what we are going to do, though, is build a docker image. Or, I should say, an OCI image.
This is still a series about Nix, but again: because the best way to see the benefits of Nix is to do it without Nix first, we'll use only Docker's tooling to build the image.
Disclaimer:
Because I used to work for fly.io, I still benefit from an employee discount at the time of this writing: I don't have to pay for anything deployed there for now.
fly.io is still sponsoring me for developing hring, but this isn't a sponsored post. It's just a good fit for what we're doing here, with a generous free tier.
Wait wait wait, so we're not talking about nix yet?
Well, no! The service we have is pretty simple, and I want to complicate things a bit, to show how things would work in both the Dockerfile and the nix scenario.
And because I don't like contrived examples, we're going to do something somewhat real-world: we're going to geo-locate visitors, and track how many visits we get from each country.
I sold you on some additional functionality for catscii
last chapter, and we
got caught up in private registry / docker shenanigans, so, now, let's resume
web development as promised.
Adding geolocation
We kinda left the locat
crate stubby, it doesn't actually do any IP to
location lookups. It doesn't even have a dependency on a crate that do
that.
Remember the snapshot we made allll the way back in Part 1? Now's the time to use it.
Well, make sure you've committed and pushed all your changes, but when you're ready, let's go back in time to before we installed anything catscii-specific in our VM.
This should emulate the experience of a colleague onboarding onto the project well enough!
In the previous chapter, we've made a nix "dev shell" that contained the fly.io command-line utility, "flyctl".
That said, that's not how I want us to define a dev shell.
Our current solution has issues. I don't like that it has import <nixpkgs>
.
Which version of nixpkgs
is that? The one you're on? Who knows what that is.
There it is. The final installment.
Over the course of this series, we've built a very useful Rust web service that shows us colored ASCII art cats, and we've packaged it with docker, and deployed it to https://fly.io.
We did all that without using nix
at all, and then in the last few chapters,
we've learned to use nix
, and now it's time to tell goodbye,
along with this whole-ass :
We've achieved our goals already with this series: we have a web service written in Rust, built into a Docker image with nix, with a nice dev shell, that we can deploy to fly.io.
But there's always room for improvement, and so I wanted to talk about a few things we didn't bother doing in the previous chapters.
Making clash-geoip
available in the dev shell
This series is complete.