Skip to content

Containers 🆕

New in version 0.6.


Creating containers is only possible on Linux at the moment.

devenv container <name> can generate an OCI container from your development environment.

By default shell and processes containers are predefined and you can craft your own!

Examples of what devenv container can do:

  • devenv container shell: Generate a container starting the environment, equivalent of using devenv shell.
  • devenv container processes: Generate a container starting processes, equivalent of using devenv up.
  • devenv container <name> --registry docker:// --copy: Copy the container <name> into the GitHub package registry.
  • devenv container <name> --docker-run: Run the container <name> using Docker.

Entering the development environment

Given a simple environment using Python:

  name = "simple-python-app";

  languages.python.enable = true;

Generate a container specification that enters the environment:

$ devenv container shell

Let's test it locally using Docker:

$ devenv container shell --docker-run
(devenv) bash-5.2# python
Python 3.10.9 (main, Dec  6 2022, 18:44:57) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

Running processes

It's common deployment strategy to run all processes as the entrypoint to the container.

  name = "myapp";

  packages = [ pkgs.procps ];

  processes.hello-docker.exec = "while true; do echo 'Hello Docker!' && sleep 1; done";
  processes.hello-nix.exec = "while true; do echo 'Hello Nix!' && sleep 1; done";

  # Exclude the source repo to make the container smaller.
  containers."processes".copyToRoot = null; 

You can now copy the newly created image and start the container:

$ devenv container processes --docker-run
06:30:06 system         | hello-docker.1 started (pid=15)
06:30:06 hello-docker.1 | Hello Docker!
06:30:06 system         | hello-nix.1 started (pid=16)
06:30:06 hello-nix.1    | Hello Nix!
06:30:07 hello-nix.1    | Hello Nix!
06:30:07 hello-docker.1 | Hello Docker!
06:30:08 hello-nix.1    | Hello Nix!
06:30:08 hello-docker.1 | Hello Docker!

Running a single process

You can specify the command to run when the container starts (instead of entering the default development environment):

  processes.serve.exec = "python -m http.server";

  containers."serve".name = "myapp";
  containers."serve".startupCommand = config.processes.serve.exec;
$ devenv container serve --docker-run

Running artifacts

If you're building binaries as part of the development environment, you can choose to only include those in the final image:

  # watch local changes and build the project to ./dist = "${pkgs.watchexec}/bin/watchexec my-build-tool";

  containers."prod".copyToRoot = ./dist;
  containers."prod".startupCommand = "/mybinary serve";
$ devenv container prod --docker-run 

Copying container to a registry

To copy a container into a registry, pass it a --copy operation:

$ devenv container processes --copy --registry docker:// 

Another common example is deploying to Any arguments passed to --copy-args are forwarded to skopeo copy:

$ devenv container processes --copy --registry docker:// --copy-args="--dest-creds x:$(flyctl auth token)"

You can also specify these options declaratively:

  containers."processes".registry = "docker://";
  containers."processes".defaultCopyArgs = [
    "x:\"$(${pkgs.flyctl}/bin/flyctl auth token)\""

See example how to get started.

Changing environment based on the build type

If we wanted to provide openssl package to native and container environments, but git only for native environments:

{ pkgs, config, lib, ... }:

  packages = [ pkgs.openssl ] 
    ++ lib.optionals (!config.container.isBuilding) [ pkgs.git ];

You can also conditionalize based on the particular container that is being built, e.g. config.containers."processes".isBuilding.