Setting up a dev environment for PostgreSQL with nixos-container
12 July 2020I’ve been using NixOS for about a month now, and one of my favourite aspects is using lorri and direnv to avoid cluttering up my user or system environments with packages I only need for one specific project. However, they don’t work quite as well when you need access to a service like PostgreSQL, since all they can do is install packages to an isolated environment, not run a whole RDBMS in the background.
For that, I have found using nixos-container
works very
well. It’s documented in the NixOS manual.
We’ll be using it in ‘imperative mode’, since editing the system
configuration is the exact thing we don’t want to do. You will need
sudo
/root
access to start containers, and I’ll
assume you have lorri and direnv set up (e.g. via
services.lorri.enable = true
in your home-manager
config).
We’ll make a directory to work in, and get started in the standard lorri way:
$ mkdir foo && cd foo
$ lorri init
Jul 11 21:23:48.117 INFO wrote file, path: ./shell.nix
Jul 11 21:23:48.117 INFO wrote file, path: ./.envrc
Jul 11 21:23:48.117 INFO done
direnv: error /home/josh/c/foo/.envrc is blocked. Run `direnv allow` to approve its content
$ direnv allow .
Jul 11 21:24:10.826 INFO lorri has not completed an evaluation for this project yet, expr: /home/josh/c/foo/shell.nix
direnv: export +IN_NIX_SHELL
Now we can edit our shell.nix
to install PostgreSQL to
access it as a client:
# shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.postgresql
];
}
Save that and lorri will start installing it in the background.
Now, we can define our container, by providing its configuration in a
file. I have called it container.nix
, but I don’t think
there’s a standard name for it like there is for shell.nix
.
Here it is:
# container.nix
{ pkgs, ... }:
{
system.stateVersion = "20.09";
networking.firewall.allowedTCPPorts = [ 5432 ];
services.postgresql = {
enable = true;
enableTCPIP = true;
extraPlugins = with pkgs.postgresql.pkgs; [ postgis ];
authentication = "host all all 10.233.0.0/16 trust";
ensureDatabases = [ "foo" ];
ensureUsers = [{
name = "foo";
ensurePermissions."DATABASE foo" = "ALL PRIVILEGES";
}];
};
}
It’s important to make sure the firewall opens the port so that we
can actually access PostgreSQL, and I’ve also installed the postgis
extension for geospatial tools. The authentication
line
means that any user on any container can authenticate as any user with
no checking: fine for development purposes, but obviously be careful not
to expose this to the internet! Finally, we set up a user and a database
to do our work in.
Now, we can actually create and start the container using the
nixos-container
tool itself. This is the only step that
requires admin rights.
$ sudo nixos-container create foo --config-file container.nix
$ sudo nixos-container start foo
By now, lorri should have finished installing PostgreSQL into your
local environment, so once nixos-container
has finished
running, you should be able to access the new database inside the
container:
$ psql -U foo -h $(nixos-container show-ip foo) foo
psql (11.8)
Type "help" for help.
foo=> \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
foo | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres +
| | | | | postgres=CTc/postgres+
| | | | | foo=CTc/postgres
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
And there we go! We have a container that we can access from the command line, or from an app, and we didn’t need to install PostgreSQL globally. We can even have multiple containers like this for different projects, and they’ll all use the same Nix store for binaries but have completely isolated working environments.
The nixos-container
tool itself is a fairly thin wrapper
around systemd
(the containers themselves work via
systemd-nspawn
). The containers won’t auto-start, and you
have to use systemctl to make that happen:
$ sudo systemctl enable container@foo.service
As a final flourish, we can save having to type in the user, host IP
and database with very little effort, since we’re already using direnv
and most tools can take their PostgreSQL configuration from some
standard environment variables. We just have to add them to our
.envrc
file, and then re-allow it.
$ cat .envrc
PGHOST=$(nixos-container show-ip foo)
PGUSER=foo
PGDATABASE=foo
export PGHOST PGUSER PGDATABASE
eval "$(lorri direnv)"
$ direnv allow .
$ psql
psql (11.8)
Type "help" for help.
foo=>