Words with Podman

I’ve installed Podman, and configured rootless. Let’s get Words generated inside a container.

We’ll be making use of Pods, which will be familiar to anyone who’s used Kubernetes before. A full-blown container orchestration system sounds unhinged but it’s the official way so let’s give it a whirl.

Create a pod

We’ll start by creating a pod to contain the JavaScript ecosystem (namely Parcel, which optimises the static content generated by a hodge-podge of ELisp I’ve cobbled together).

podman pod create --name words -p 1234:1234

Let’s take a look at that pod:

podman pod ls
POD ID        NAME    STATUS   CREATED        INFRA ID      # OF CONTAINERS
c7efd4ede37a  words   Created  5 seconds ago  e2b3df0b378c  1

Create a container

podman-run

podman run --help
Run a command in a new container

Description:
  Runs a command in a new container from the given image

Usage:
  podman run [options] IMAGE [COMMAND [ARG...]]

Examples:
  podman run imageID ls -alF /etc
  podman run --network=host imageID dnf -y install java
  podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash

Options:
      --add-host strings                         Add a custom host-to-IP mapping (host:ip) (default [])
      --annotation strings                       Add annotations to container (key:value)
  -a, --attach strings                           Attach to STDIN, STDOUT or STDERR
      --authfile string                          Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override
      --blkio-weight string                      Block IO weight (relative weight) accepts a weight value between 10 and 1000.
      --blkio-weight-device DEVICE_NAME:WEIGHT   Block IO weight (relative device weight, format: DEVICE_NAME:WEIGHT)
      --cap-add strings                          Add capabilities to the container
      --cap-drop strings                         Drop capabilities from the container
      --cgroup-conf strings                      Configure cgroup v2 (key=value)
      --cgroup-parent string                     Optional parent cgroup for the container
      --cgroupns string                          cgroup namespace to use
      --cgroups string                           control container cgroup configuration ("enabled"|"disabled"|"no-conmon"|"split") (default "enabled")
      --cidfile string                           Write the container ID to the file
      --conmon-pidfile string                    Path to the file that will receive the PID of conmon
      --cpu-period uint                          Limit the CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int                            Limit the CPU CFS (Completely Fair Scheduler) quota
      --cpu-rt-period uint                       Limit the CPU real-time period in microseconds
      --cpu-rt-runtime int                       Limit the CPU real-time runtime in microseconds
      --cpu-shares uint                          CPU shares (relative weight)
      --cpus float                               Number of CPUs. The default is 0.000 which means no limit
      --cpuset-cpus string                       CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string                       Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
  -d, --detach                                   Run container in background and print container ID
      --detach-keys [a-Z]                        Override the key sequence for detaching a container. Format is a single character [a-Z] or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\`, `]`, `^` or `_` (default "ctrl-p,ctrl-q")
      --device strings                           Add a host device to the container
      --device-cgroup-rule strings               Add a rule to the cgroup allowed devices list
      --device-read-bps strings                  Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
      --device-read-iops strings                 Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)
      --device-write-bps strings                 Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
      --device-write-iops strings                Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)
      --disable-content-trust                    This is a Docker specific option and is a NOOP
      --dns strings                              Set custom DNS servers
      --dns-opt strings                          Set custom DNS options
      --dns-search strings                       Set custom DNS search domains
      --entrypoint string                        Overwrite the default ENTRYPOINT of the image
  -e, --env stringArray                          Set environment variables in container (default [PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin,TERM=xterm])
      --env-file strings                         Read in a file of environment variables
      --env-host                                 Use all current host environment variables in container
      --expose strings                           Expose a port or a range of ports
      --gidmap strings                           GID map to use for the user namespace
      --group-add strings                        Add additional groups to join
      --health-cmd string                        set a healthcheck command for the container ('none' disables the existing healthcheck)
      --health-interval string                   set an interval for the healthchecks (a value of disable results in no automatic timer setup) (default "30s")
      --health-retries uint                      the number of retries allowed before a healthcheck is considered to be unhealthy (default 3)
      --health-start-period string               the initialization time needed for a container to bootstrap (default "0s")
      --health-timeout string                    the maximum time allowed to complete the healthcheck before an interval is considered failed (default "30s")
  -h, --hostname string                          Set container hostname
      --http-proxy                               Set proxy environment variables in the container based on the host proxy vars (default true)
      --image-volume string                      Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore") (default "bind")
      --init                                     Run an init binary inside the container that forwards signals and reaps processes
      --init-path string                         Path to the container-init binary
  -i, --interactive                              Keep STDIN open even if not attached
      --ip string                                Specify a static IPv4 address for the container
      --ipc string                               IPC namespace to use
      --kernel-memory <number>[<unit>]           Kernel memory limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
  -l, --label stringArray                        Set metadata on container
      --label-file strings                       Read in a line delimited file of labels
      --log-driver string                        Logging driver for the container
      --log-opt strings                          Logging driver options
      --mac-address string                       Container MAC address (e.g. 92:d0:c6:0a:29:33)
  -m, --memory <number>[<unit>]                  Memory limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
      --memory-reservation <number>[<unit>]      Memory soft limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
      --memory-swap string                       Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --memory-swappiness int                    Tune container memory swappiness (0 to 100, or -1 for system default) (default -1)
      --mount stringArray                        Attach a filesystem mount to the container
      --name string                              Assign a name to the container
      --network string                           Connect a container to a network (default "slirp4netns")
      --network-alias strings                    Add network-scoped alias for the container
      --no-healthcheck                           Disable healthchecks on container
      --no-hosts                                 Do not create /etc/hosts within the container, instead use the version from the image
      --oom-kill-disable                         Disable OOM Killer
      --oom-score-adj int                        Tune the host's OOM preferences (-1000 to 1000)
      --override-arch ARCH                       use ARCH instead of the architecture of the machine for choosing images
      --override-os OS                           use OS instead of the running OS for choosing images
      --override-variant string                  Use _VARIANT_ instead of the running architecture variant for choosing images
      --pid string                               PID namespace to use
      --pids-limit int                           Tune container pids limit (set 0 for unlimited, -1 for server defaults)
      --pod string                               Run container in an existing pod
      --pod-id-file string                       Read the pod ID from the file
      --preserve-fds uint                        Pass a number of additional file descriptors into the container
      --privileged                               Give extended privileges to container
  -p, --publish strings                          Publish a container's port, or a range of ports, to the host (default [])
  -P, --publish-all                              Publish all exposed ports to random ports on the host interface
      --pull string                              Pull image before creating ("always"|"missing"|"never") (default "missing")
  -q, --quiet                                    Suppress output information when pulling images
      --read-only                                Make containers root filesystem read-only
      --read-only-tmpfs                          When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp (default true)
      --replace                                  If a container with the same name exists, replace it
      --restart string                           Restart policy to apply when a container exits ("always"|"no"|"on-failure"|"unless-stopped")
      --rm                                       Remove container (and pod if created) after exit
      --rmi                                      Remove container image unless used by other containers
      --rootfs                                   The first argument is not an image but the rootfs to the exploded container
      --sdnotify string                          control sd-notify behavior ("container"|"conmon"|"ignore") (default "container")
      --seccomp-policy string                    Policy for selecting a seccomp profile (experimental) (default "default")
      --security-opt stringArray                 Security Options
      --shm-size <number>[<unit>]                Size of /dev/shm (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) (default "65536k")
      --sig-proxy                                Proxy received signals to the process (default true)
      --stop-signal string                       Signal to stop a container. Default is SIGTERM
      --stop-timeout uint                        Timeout (in seconds) to stop a container. Default is 10 (default 10)
      --subgidname string                        Name of range listed in /etc/subgid for use in user namespace
      --subuidname string                        Name of range listed in /etc/subuid for use in user namespace
      --sysctl strings                           Sysctl options
      --systemd string                           Run container in systemd mode ("true"|"false"|"always") (default "true")
      --tmpfs tmpfs                              Mount a temporary filesystem (tmpfs) into a container
  -t, --tty                                      Allocate a pseudo-TTY for container
      --tz string                                Set timezone in container
      --uidmap strings                           UID map to use for the user namespace
      --ulimit strings                           Ulimit options
      --umask string                             Set umask in container (default "0022")
  -u, --user string                              Username or UID (format: <name|uid>[:<group|gid>])
      --userns string                            User namespace to use
      --uts string                               UTS namespace to use
  -v, --volume stringArray                       Bind mount a volume into the container
      --volumes-from stringArray                 Mount volumes from the specified container(s)
  -w, --workdir string                           Working directory inside the container

That’s a lot of options! Everything from network configuration to resource limits; just as you’d expect when you’re creating a container.

podman-rm

Given I’m running things with Podman, I need to be careful not to clobber things (without some careful massaging, publishing this file creates new containers over and over, and appends to the Kubernetes YAML we generate a little later).

podman-run

Now we can run a container inside our words pod via podman-run(1).

podman run \
  --detach \
  --name=words-yarn-dev \
  --pod=words \
  --restart=on-failure \
  --volume="$dir:/usr/src/app:rw" \
  --workdir=/usr/src/app \
  docker://docker.io/library/node:alpine \
  yarn dev
a1a8c920b3248a0c527b99c8ce02ba8ff9989d848deb28aa2409d49895dc1352
podman pod inspect words | jq .
{
  "Id": "c7efd4ede37aa765d7b9ceb551535c6001fba879c22ae385225f0ad167976635",
  "Name": "words",
  "Created": "2021-02-07T16:48:48.994240915Z",
  "CreateCommand": [
    "podman",
    "pod",
    "create",
    "--name",
    "words",
    "-p",
    "1234:1234"
  ],
  "State": "Running",
  "Hostname": "words",
  "CreateCgroup": true,
  "CgroupParent": "/libpod_parent",
  "CgroupPath": "/libpod_parent/c7efd4ede37aa765d7b9ceb551535c6001fba879c22ae385225f0ad167976635",
  "CreateInfra": true,
  "InfraContainerID": "e2b3df0b378c08bc08887e6461340fa92b5f37c9b1ad24be990e5b80db075e1d",
  "InfraConfig": {
    "PortBindings": {
      "1234/tcp": [
        {
          "HostIp": "",
          "HostPort": "1234"
        }
      ]
    },
    "HostNetwork": false,
    "StaticIP": "",
    "StaticMAC": "",
    "NoManageResolvConf": false,
    "DNSServer": null,
    "DNSSearch": null,
    "DNSOption": null,
    "NoManageHosts": false,
    "HostAdd": null,
    "Networks": null,
    "NetworkOptions": null
  },
  "SharedNamespaces": [
    "uts",
    "ipc",
    "net"
  ],
  "NumContainers": 2,
  "Containers": [
    {
      "Id": "a1a8c920b3248a0c527b99c8ce02ba8ff9989d848deb28aa2409d49895dc1352",
      "Name": "words-yarn-dev",
      "State": "running"
    },
    {
      "Id": "e2b3df0b378c08bc08887e6461340fa92b5f37c9b1ad24be990e5b80db075e1d",
      "Name": "c7efd4ede37a-infra",
      "State": "running"
    }
  ]
}

Making this reusable

Now we can export the configuration for our pod into YAML for the next time we want to see some words.

podman generate kube words
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-2.2.1
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-02-07T16:50:18Z"
  labels:
    app: words
  name: words
spec:
  containers:
  - command:
    - yarn
    - dev
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: container
      value: podman
    - name: YARN_VERSION
      value: 1.22.5
    - name: NODE_VERSION
      value: 15.8.0
    - name: HOSTNAME
      value: words
    image: docker.io/library/node:alpine
    name: words-yarn-dev
    ports:
    - containerPort: 1234
      hostPort: 1234
      protocol: TCP
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_NET_RAW
        - CAP_AUDIT_WRITE
      privileged: false
      readOnlyRootFilesystem: false
      seLinuxOptions: {}
    volumeMounts:
    - mountPath: /usr/src/app
      name: home-jcf-code-words
    workingDir: /usr/src/app
  restartPolicy: OnFailure
  volumes:
  - hostPath:
      path: /home/jcf/code/words
      type: Directory
    name: home-jcf-code-words
status: {}
---
metadata:
  creationTimestamp: null
spec: {}
status:
  loadBalancer: {}

Mention of my $HOME in that configuration means it’s anything but portable unfortunately. That said, it will work next time I’m writing on this machine (or on any similar machine).