Getting Docker Images in a More Declarative Way
Getting Docker Images in a More Declarative Way
This post serves as a sequel to my earlier article, How Many Ways to Get a Docker Image?.
Introduction
The inspiration for this post came from pondering how to integrate Nix with Kubernetes to enhance efficiency and reduce costs. After brainstorming, I concluded that Kubernetes already handles networking and storage well. However, there’s room for improvement in how it manages execution environment. This post explores that avenue.
Two New Approaches
I’ll introduce two new methods for building Docker images:
-
Declarative Image Building: Unlike traditional Dockerfiles, this approach doesn’t rely on step-by-step instructions. Instead, it references the libraries that the image will require.
-
Universal Docker Images: This method can be extended to create a universal way to build Linux distribution images. Stay tuned for a future post on this topic.
Existing Methods for Reference
How Many Ways to Get a Docker Image?.
docker pull
docker commit
docker build
docker load
docker import
yum
for CentOSdebootstrap
for Debiansquashfs
for any
Declarative Image Building with Nix
Let’s assume you need an environment to interact with Redis, Kubernetes API server, Google Cloud services, and a JSON-producing HTTP API. Normally, you’d write a Dockerfile with installation instructions. However, using the Nix file below ensures that you get identical Docker images with EXACTLY SAME HASH every time you build.
$ cat hello-docker.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildImage {
name = "my-image";
tag = "latest";
copyToRoot = [
pkgs.google-cloud-sdk
pkgs.redis
pkgs.kubectl
pkgs.curl
pkgs.jq
pkgs.bashInteractive
];
config = {
Cmd = [ "/bin/bash" ];
};
}
$ nix-build hello-docker.nix
/nix/store/zicgvkjp6rgqa7v9z89r58hgxf11mxmj-docker-image-my-image.tar.gz
$ docker load < result
Loaded image: my-image:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-image latest db6bf36eb8ed 53 years ago 1.42GB
This approach guarantees reproducible builds, which is essential for CI/CD pipelines that aim for more than just script execution.
Using Nix in Shebang
Traditionally, the shebang (#!
) in shell scripts specifies the interpreter, commonly /bin/sh
or /bin/bash
. However, you can use any interpreter, like Python or PowerShell. With nix-shell
as the interpreter, you can specify dependencies right before script execution.
Here’s a Kubernetes Pod YAML to demonstrate:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nixos/nix
command: ["/root/.nix-profile/bin/bash", "-c"]
args:
- |
cp /etc/config/my-script.sh /run.sh && chmod +x $_
/run.sh
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: my-script-configmap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: my-script-configmap
data:
my-script.sh: |
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p kubectl
kubectl --help
tail -f /dev/null
This example is minimal but illustrates the core concept. More complex scripts can be written in the ConfigMap, and additional packages can be specified.
Conclusion
By leveraging Nix and Kubernetes, you can make your container orchestration more efficient and reproducible. Whether you’re building images or specifying dependencies in scripts, these methods offer a more declarative approach.