Deploying a Simple NixOps Website

Posted on September 25, 2015

Full disclaimer: I am currently an Arch Linux user. My experimentation thus far is with using nix on Arch Linux. TL;DR: I am thoroughly enjoying my exploration into Nix (and, by extension/deployment, into NixOS).

The walkthroughs that I’ve found seem geared towards creating a server which runs some 3rd-party service – sometimes that service was one not included in nixpkgs, but that was as close to deploying a simple custom website as I could find.

On the other side of the equation, there were the continuously integrated projects with nix expressions everywhere and a central repository from which to pull authoritative source. They didn’t really differ much from the previous class of tutorials.

There is a significant gap in what I was able to find. Here’s the tutorial that I needed:

Deploying A (Really) Simple NixOps Website

After three attempts, I found a very constrained problem that I wanted to attack with nixops: I needed to get a simple POST-accepting PHP script deployed some(any-)where. This script contains proprietary information, and is so trivial that it will never make it into the nixpkgs repository. Nor should it live longer than a couple weeks, so there’s no need to build support infrastructure for it.

First, get your data together. I made a project directory (the name is unimportant) and then created a subdirectory called data. Imaginative, eh?

$ find .
./
./data
./data/server.php

Now, Nix (the package manager) is going to need a way to install this. Let’s create an expression for that called application.nix. This is the secret sauce that makes our cobbled together collection of files into a full-fledged nix package.

with import <nixpkgs> {};

stdenv.mkDerivation {
  name = "myWebApplication";
  src  = ./data;
  installPhase = ''
    mkdir -p "$out/web"
	cp -ra * "$out/web"
  '';
}

So, now our project looks like this:

$ find .
./
./application.nix
./data
./data/server.php

Pretty easy. The expression only has an install phase, and all it does is copy the data directory to a directory called web in the installation. Let’s test that that works:

$ nix-build application.nix
/nix/store/lgf2lagvissnzkb10ddnqvbxm21mnr8s-myWebApplication

Awesome. We’re done with raw nix expressions. Now we move into related expressions for nixops – which are still nix epressions, but wrapped in a network description.

We will need a cofiguration for the machine to run this on – I call this the “requirements” expression. I choose Apache, ’cause it’s a one-stop shop for such a simple job. Pay attention to the magic in services.http.documentRoot.

{
  network.description = "My Stupid-Simple WebSite"

  appserver =
    { stdenv, config, pkgs, ... }:
	let myActualContent = import ./application.nix; in
	{
	  environment.systemPackages = [
	    pkgs.apacheHttpd
		myActualContent
	  ];

	  networking.firewall.allowedTCPPorts = [ 22, 80 ];

	  services.openssh.enable = true;

	  services.httpd.enable       = true;
	  services.httpd.enablePHP    = true;
	  services.httpd.adminAddr    = "webmaster@foosoft.us";
	  services.httpd.documentRoot = "${myActualContent}/web";

	  users.extraUsers.root.openssh.authorizedKeys.keys = [
        "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP5tbk8oBvjFqQN6fSirPVNYlioI4HXLNazEWczuBJQMq3Tn16ACADJIrgmA+jfGgKmFXBqcFfossPZA5lExUuo= greyson@astaroth"
	  ];
	};
}

Now our project looks like this:

$ find .
./
./application.nix
./requirements.nix
./data
./data/server.php

How can we test this? Well, actually, I don’t think we can. Not until we have a description of the machine to which to deploy it. I’d like to test this locally before opening it up to the world in general. Let’s make a file called virtualbox-spec.nix:

{
  appserver =
    { config, pkgs, ... }:
	{ deployment.targetEnv = "virtualbox";
	  deployment.virtualbox.memorySize = 1024;
	};
}

Now we can start to do some interesting stuff – we’ve defined the machine we’re running on and what should run on it (including our simple website) in just two files which we combine with a nixops command:

$ nixops create ./requirements.nix ./virtualbox-spec.nix -d easyname-dev
created deployment ‘ff139400-63bb-11e5-8112-75c69b353f69’
ff139400-63bb-11e5-8112-75c69b353f69

This outputs status to stderr, and the GUID to stdout (so that you could capture it into a variable). Nice. Let’s get some better gratification:

$ nixops list
+--------------------------------------+-------------------+------------------------+------------+------------+
| UUID                                 | Name              | Description            | # Machines |    Type    |
+--------------------------------------+-------------------+------------------------+------------+------------+
| ff139400-63bb-11e5-8112-75c69b353f69 | easyname          | Unnamed NixOps network |          0 |            |
+--------------------------------------+-------------------+------------------------+------------+------------+

Amazing. The next question is, does it boot?

$ nixops deploy -d easyname
appserver> creating VirtualBox VM...
appserver> Virtual machine 'ni ... ... ...

At this point, a virtualbox machine like you’ve never seen should be popping up and if your VirtualBox networks are all up, nixops should find it shortly after boot and complete the build-out. You can check it by sshing into the box:

$ nixops ssh appserver
[root@appserver:~]# echo 'yay'
yay

You can find the IP address using nixops info -d easyname and then check that in your browser – yes, it’s all there.

EC2

So, the next machine configuration we’ll make is for EC-2. I’ll call it application-incloud.nix.

let
  region = "us-east-1";
  accessKeyId = "myfoosoftkey";

  ec2 =
    { resources, ... }:
	{ deployment.targetEnv = "ec2";
	  deployment.ec2 = {
		inherit region accessKeyId;
	    associatePublicIpAddress = true;
		subnetId = "subnet-XXXXXXXX";
		instanceType = "t2.micro";
		keyPair = resources.ec2KeyPairs.my-key-pair;
      };
    };
in {
  appserver = ec2;
  resources.ec2KeyPairs.my-key-pair = { inherit region accessKeyId; };
}

As I said in my last post, I have never been a cloud netizen before discovering NixOS earlier this week. I have always left the EC2 deployment part of my applications to “more qualified” individuals – by which I mean the web developers. So I ended up chasing my tail quite a bit before this worked:

$ nixops create ./requirements.nix ./application-incloud.nix -d quickserv-prod
$ nixops deploy -d quickserv-prod

The AWS setup process by which I got there was convoluted, so I’ll just list some of the pitfalls that I encountered: