This short article documents how I run
Isso, the commenting system
used by this blog, inside a Docker container on
NixOS, a Linux
distribution built on top of Nix. Nix is a declarative package
manager for Linux and other Unix systems.
While NixOS 20.09 includes a
derivation for Isso, it is
unfortunately broken and relies on Python 2. As I am also using a fork
of Isso, I have built my own derivation, heavily inspired by the one
in
master:
issoPackage = with pkgs.python3Packages; buildPythonPackage rec
pname = "isso";
version = "custom";
src = pkgs.fetchFromGitHub
# Use my fork
owner = "vincentbernat";
repo = pname;
rev = "vbe/master";
sha256 = "0vkkvjcvcjcdzdj73qig32hqgjly8n3ln2djzmhshc04i6g9z07j";
;
propagatedBuildInputs = [
itsdangerous
jinja2
misaka
html5lib
werkzeug
bleach
flask-caching
];
buildInputs = [
cffi
];
checkInputs = [ nose ];
checkPhase = ''
$ python.interpreter setup.py nosetests
'';
;
I want to run Isso through
Gunicorn. To this effect, I build a
Python environment combining Isso and Gunicorn. Then, I can invoke the
latter with
"$ issoEnv /bin/gunicorn", like with a
virtual
environment.
issoEnv = pkgs.python3.buildEnv.override
extraLibs = [
issoPackage
pkgs.python3Packages.gunicorn
pkgs.python3Packages.gevent
];
;
Before building a Docker image, I also need to specify the
Isso configuration file for Isso:
issoConfig = pkgs.writeText "isso.conf" ''
[general]
dbpath = /db/comments.db
host =
https://vincent.bernat.ch
http://localhost:8080
notify = smtp
[ ]
'';
NixOS comes with a
convenient tool to build a Docker image without
a
Dockerfile:
issoDockerImage = pkgs.dockerTools.buildImage
name = "isso";
tag = "latest";
extraCommands = ''
mkdir -p db
'';
config =
Cmd = [ "$ issoEnv /bin/gunicorn"
"--name" "isso"
"--bind" "0.0.0.0:$ port "
"--worker-class" "gevent"
"--workers" "2"
"--worker-tmp-dir" "/dev/shm"
"--preload"
"isso.run"
];
Env = [
"ISSO_SETTINGS=$ issoConfig "
"SSL_CERT_FILE=$ pkgs.cacert /etc/ssl/certs/ca-bundle.crt"
];
;
;
Because we refer to the
issoEnv derivation in
config.Cmd, the
whole derivation, including Isso and Gunicorn, is copied inside the
Docker image. The same applies for
issoConfig, the configuration
file we created earlier, and
pkgs.cacert, the derivation containing
trusted root certificates. The resulting image is 171 MB once
installed, which is comparable to the Debian Buster image generated by
the official
Dockerfile.
NixOS features an abstraction to run Docker containers. It is not
currently documented in NixOS manual but you can look at the
source
code of the module for the available options. I choose to use
Podman instead of Docker as the backend because it does not
require running an additional daemon.
virtualisation.oci-containers =
backend = "podman";
containers =
isso =
image = "isso";
imageFile = issoDockerImage;
ports = ["127.0.0.1:$ port :$ port "];
volumes = [
"/var/db/isso:/db"
];
;
;
;
A systemd unit file is automatically created to run and supervise the container:
$ systemctl status podman-isso.service
podman-isso.service
Loaded: loaded (/nix/store/a66gzqqwcdzbh99sz8zz5l5xl8r8ag7w-unit->
Active: active (running) since Sun 2020-11-01 16:04:16 UTC; 4min 44s ago
Process: 14564 ExecStartPre=/nix/store/95zfn4vg4867gzxz1gw7nxayqcl>
Main PID: 14697 (podman)
IP: 0B in, 0B out
Tasks: 10 (limit: 2313)
Memory: 221.3M
CPU: 10.058s
CGroup: /system.slice/podman-isso.service
14697 /nix/store/pn52xgn1wb2vr2kirq3xj8ij0rys35mf-podma>
14802 /nix/store/7vsba54k6ag4cfsfp95rvjzqf6rab865-conmo>
nov. 01 16:04:17 web03 podman[14697]: container init (image=localhost/isso:latest)
nov. 01 16:04:17 web03 podman[14697]: container start (image=localhost/isso:latest)
nov. 01 16:04:17 web03 podman[14697]: container attach (image=localhost/isso:latest)
nov. 01 16:04:19 web03 conmon[14802]: INFO: connected to SMTP server
nov. 01 16:04:19 web03 conmon[14802]: INFO: connected to https://vincent.bernat.ch
nov. 01 16:04:19 web03 conmon[14802]: [INFO] Starting gunicorn 20.0.4
nov. 01 16:04:19 web03 conmon[14802]: [INFO] Listening at: http://0.0.0.0:8080 (1)
nov. 01 16:04:19 web03 conmon[14802]: [INFO] Using worker: gevent
nov. 01 16:04:19 web03 conmon[14802]: [INFO] Booting worker with pid: 8
nov. 01 16:04:19 web03 conmon[14802]: [INFO] Booting worker with pid: 9
As the last step, we configure Nginx to forward requests for
comments.luffy.cx to the container. NixOS provides a simple
integration to grab a
Let s Encrypt certificate.
services.nginx.virtualHosts."comments.luffy.cx" =
root = "/data/webserver/comments.luffy.cx";
enableACME = true;
forceSSL = true;
extraConfig = ''
access_log /var/log/nginx/comments.luffy.cx.log anonymous;
'';
locations."/" =
proxyPass = "http://127.0.0.1:$ port ";
extraConfig = ''
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_hide_header Set-Cookie;
proxy_hide_header X-Set-Cookie;
proxy_ignore_headers Set-Cookie;
'';
;
;
security.acme.certs."comments.luffy.cx" =
email = lib.concatStringsSep "@" [ "letsencrypt" "vincent.bernat.ch" ];
;
While I still struggle with Nix and NixOS, I am convinced this is how
declarative infrastructure should be done. I like how in
one
single file, I can define the derivation to build Isso, the
configuration, the Docker image, the container definition, and the
Nginx configuration. The Nix language is used both for building
packages and for managing configurations.
Moreover, the Docker image is updated
automatically like a regular NixOS host. This solves an issue plaguing
the Docker ecosystem: no more stale images! My next step would be to
combine this approach with
Nomad, a simple orchestrator to deploy
and manage containers.