Skip to content

Ubuntu 24.04 Rootfs: Base Image Build and Integration for the Iximiuz Labs

Context

Ubuntu 24.04 Rootfs is the base image for all SilverStack iximiuz playground machines - every other rootfs in this stack (FROM ghcr.io/ibtisam-iq/ubuntu-24-04-rootfs:latest) builds on top of it.

It is consumed directly by child images such as Dev Machine, Jenkins, Nexus, and SonarQube. Those images assume this base is already systemd-enabled, SSH-ready, and equipped with a curated DevOps toolset.

Important: This image is not a regular Docker application image. It is a microVM rootfs designed for the iximiuz Labs platform, which boots it as a full virtual machine using its own kernel and init system. When the platform boots the VM, systemd becomes PID 1 automatically - the image itself does not need a CMD or ENTRYPOINT. Attempting to validate this image with plain docker run will not produce a working systemd environment; see Verification for the correct approach.

The image is defined under:


Objectives

Ubuntu 24.04 Rootfs must:

  • Provide a fully unminimized, systemd-enabled Ubuntu 24.04 environment that behaves like a real server inside iximiuz microVMs.
  • Configure SSH with key-based authentication only, optimized for low-latency playground access.
  • Deliver a sane default shell environment: custom PS1 prompt, bash completion, vim, fzf, ripgrep, and Git configuration helpers.
  • Ship a curated DevOps toolset (arkade, jq/yq/fx, task/just, btop, cfssl, code-server, websocat) that child images can rely on without re-installing.
  • Create a consistent non-root interactive user ($USER, default ibtisam) with tuned .bashrc, .gitconfig, and .vimrc.
  • Be built reproducibly via GitHub Actions, published multi-arch to GHCR as ghcr.io/ibtisam-iq/ubuntu-24-04-rootfs.

Architecture / Conceptual Overview

How iximiuz Uses This Image

The iximiuz Labs platform boots playground machines by mounting the OCI image as a block device filesystem (rootfs) for a microVM. The platform's own kernel and bootloader handle VM initialization - this is not Docker container execution. This means:

  • systemd as PID 1 is guaranteed by the platform's boot process, not by anything inside the image.
  • SSH host keys are intentionally absent from the image (deleted during build). The iximiuz platform regenerates them at VM first boot.
  • Machine IDs (/etc/machine-id, /var/lib/dbus/machine-id) are intentionally emptied during build so each VM generates its own unique identity on first boot.
  • /.dockerenv is removed during build to prevent systemd from treating the environment as a container.

What the Image Provides

The base rootfs:

  1. Starts from ubuntu:24.04 and runs unminimize to restore full userland (man pages, locales, standard tools).
  2. Installs systemd, SSH, and a standard Linux troubleshooting toolkit.
  3. Cleans container-specific artifacts and empties machine IDs.
  4. Adds a systemd "examiner" service (via set-up-systemd-examiner-service.sh) so child images can inspect service state programmatically.
  5. Installs system-wide tools via get-* scripts (arkade, common CLIs, btop, cfssl, websocat, fx).
  6. Installs user-specific tools and customizations for both root and the non-root $USER.

Child images can safely assume:

  • systemd is the init system and manages services.
  • SSH is configured and enabled (host keys are regenerated by the platform at boot).
  • The interactive $USER exists with a consistent shell experience.
  • Core utility tools and the examiner service are already present.

Key Design Decisions

  • Unminimized Ubuntu - unminimize ensures man pages, locales, and standard tools are available, which is essential for hands-on labs.

  • No CMD or ENTRYPOINT in the image - iximiuz boots the VM with its own kernel. The image must not define an entrypoint. Adding one would conflict with how the platform mounts and boots the rootfs.

  • SSH host keys intentionally deleted - rm -f /etc/ssh/ssh_host_* is done during build so every VM instance gets unique host keys. The sshd-keygen systemd units are masked to prevent Ubuntu's built-in key generation from running; the iximiuz platform handles key generation at VM boot.

  • Machine IDs emptied at build time - ensures each VM generates its own D-Bus and systemd machine identity on first boot.

  • networkd-dispatcher.service masked - reduces journald noise for lab environments where dynamic networkd reconfiguration is not needed.

  • Script-driven tooling - small focused scripts under scripts/ keep the Dockerfile readable and allow child images to reuse or override individual setup steps.

  • Single base for all rootfs images - Dev Machine, Jenkins, Nexus, SonarQube, and future images all build FROM this base, ensuring consistent behavior and eliminating duplicated setup.


Source Layout

ubuntu/
├── Dockerfile
├── welcome                            # Welcome banner (copied to $HOME/.welcome)
├── configs/
│   └── profile.d/
│       └── 00-prompt.sh               # System-wide PS1 prompt (login shells)
└── scripts/
    ├── add-user.sh                    # Creates non-root $USER
    ├── customize-bashrc.sh            # Appends to ~/.bashrc (prompt, welcome, fzf, etc.)
    ├── customize-git.sh               # Sets ~/.gitconfig defaults
    ├── customize-vimrc.sh             # Sets ~/.vimrc defaults
    ├── get-arkade.sh                  # Installs arkade
    ├── get-btop.sh                    # Installs btop at $BTOP_VERSION
    ├── get-cfssl.sh                   # Installs cfssl at $CFSSL_VERSION
    ├── get-code-server.sh             # Installs code-server (VS Code in browser)
    ├── get-common-tools.sh            # Installs jq, yq, task, just, etc.
    ├── get-fzf.sh                     # Installs fzf
    ├── get-websocat.sh                # Installs websocat at $WEBSOCAT_VERSION
    └── set-up-systemd-examiner-service.sh

Note on welcome file placement: customize-bashrc.sh appends logic to ~/.bashrc that displays and then permanently deletes ~/.welcome on the first interactive login. The COPY welcome $HOME/.welcome instruction must therefore appear after all RUN script steps so the file is not consumed during a non-interactive build layer.


Build Arguments

ARG Default Description
USER ibtisam Non-root interactive user to create
BTOP_VERSION 1.4.4 btop release version
CFSSL_VERSION 1.6.5 cfssl release version
WEBSOCAT_VERSION 1.14.1 websocat release version
ARKADE_BIN_DIR /usr/local/bin Installation path for arkade
BUILD_DATE (set by CI) OCI label: image creation timestamp
VCS_REF (set by CI) OCI label: git commit SHA

Prerequisites

  • Docker with Buildx (for multi-arch builds mirroring CI).
  • A local checkout of github.com/ibtisam-iq/silver-stack with the iximiuz/rootfs/ubuntu tree.
  • Network access to fetch packages and GitHub releases referenced by the get-* scripts.
  • For CI: a GitHub repository with packages: write permission to push to GHCR.

Build Steps

1. Local Build

From the iximiuz/rootfs/ubuntu/ directory:

IMAGE_NAME="ghcr.io/ibtisam-iq/ubuntu-24-04-rootfs:latest"

docker build \
  --build-arg USER="ibtisam" \
  --build-arg ARKADE_BIN_DIR="/usr/local/bin" \
  --build-arg BTOP_VERSION="1.4.4" \
  --build-arg CFSSL_VERSION="1.6.5" \
  --build-arg WEBSOCAT_VERSION="1.14.1" \
  -t "${IMAGE_NAME}" \
  .

The Dockerfile performs the following major steps:

Step 1 - Unminimize Ubuntu and install base packages

  • Starts from ubuntu:24.04.
  • Copies unminimize from ubuntu:22.04 (Ubuntu 24.04 dropped it from its own image).
  • Installs system packages: systemd, SSH prerequisites, and a debugging toolkit (curl, htop, mtr, traceroute, nftables, socat, etc.).
  • Runs yes | unminimize to restore the full userland.
  • Masks networkd-dispatcher.service to reduce journald noise.
  • Removes /etc/update-motd.d/*, /.dockerenv, and empties machine IDs for per-VM identity generation.
  • Sets root password to root.

Step 2 - Configure SSH

  • Installs openssh-server.
  • Appends to /etc/ssh/sshd_config: key-based auth only (AuthenticationMethods publickey), IPv4-only (AddressFamily inet), DNS disabled (UseDNS no), MaxAuthTries 50, PrintLastLog no.
  • Masks sshd-keygen@.service and sshd-keygen.target (platform regenerates host keys at VM boot).
  • Disables socket activation (ssh.socket), removes socket override files, enables ssh.service.
  • Deletes all pre-generated host keys (rm -f /etc/ssh/ssh_host_*) - each VM gets unique keys from the platform.

Step 3 - Systemd examiner service

  • Copies examiner* binaries to /usr/local/bin.
  • Runs set-up-systemd-examiner-service.sh which creates /etc/systemd/system/examiner.service and symlinks it into multi-user.target.wants.

Step 4 - System-wide prompt and tools

  • Copies configs/profile.d/00-prompt.sh to /etc/profile.d/ and marks it executable. This sets a colored PS1 for login shells. Non-login interactive shells get their PS1 from ~/.bashrc (set by customize-bashrc.sh).
  • Installs tools system-wide: get-arkade.sh, get-common-tools.sh, get-btop.sh, get-cfssl.sh, get-websocat.sh, and curl https://fx.wtf/install.sh | sh.

Step 5 - Root user customizations

  • Runs get-fzf.sh, customize-bashrc.sh, customize-git.sh, and customize-vimrc.sh for root.

Step 6 - Create non-root user

  • Runs add-user.sh to create $USER (default: ibtisam, UID 1001) with sudo group membership and passwordless NOPASSWD:ALL sudoers entry.
  • Switches to USER $USER, sets HOME=/home/$USER.

Step 7 - User-specific tools and welcome (order matters)

  • Runs get-code-server.sh, get-fzf.sh, customize-bashrc.sh, customize-git.sh (with USER=$USER), and customize-vimrc.sh for the non-root user.
  • Last instruction: COPY welcome $HOME/.welcome - placed after all script RUN steps to ensure the file is not consumed during build.

No CMD or ENTRYPOINT is set. The iximiuz platform boots the VM with its own kernel and does not execute the image as a Docker container. Setting an entrypoint here would be incorrect.


2. Build and Push via GitHub Actions

The canonical build is defined in .github/workflows/build-ubuntu-rootfs.yml.

Triggers:

  • push to main when anything under iximiuz/rootfs/ubuntu/** (except README.md) changes, or when the workflow file changes.
  • Pull requests touching the same paths.
  • Manual workflow_dispatch.

Key build behavior:

  • Authenticates to GHCR via secrets.GITHUB_TOKEN.
  • Generates tags via docker/metadata-action: latest (default branch), sha-<short-sha>, YYYY-MM-DD.
  • Runs docker/build-push-action with context: ./iximiuz/rootfs/ubuntu, platforms: linux/amd64, push: true (non-PR events), and the build-args above.
  • Prints the final image digest on completion.

Verification

Correct: Inspect the Registry Image

After a CI push or manual docker push, verify image metadata:

skopeo inspect docker://ghcr.io/ibtisam-iq/ubuntu-24-04-rootfs:latest \
  | jq '{
      name:          .Name,
      base:          .Labels["org.opencontainers.image.base.name"],
      created:       .Labels["org.opencontainers.image.created"],
      documentation: .Labels["org.opencontainers.image.documentation"],
      authors:       .Labels["org.opencontainers.image.authors"]
    }'

Correct: Boot in an iximiuz Manifest

The only valid way to verify runtime behavior (systemd, SSH, prompt, welcome) is to boot the image inside an iximiuz microVM. Use or adapt an existing manifest:

# iximiuz/manifests/ubuntu-base-test.yml
machines:
  - name: ubuntu-base
    drives:
      - source: oci://ghcr.io/ibtisam-iq/ubuntu-24-04-rootfs:latest
        mount: /
        size: 50GiB
labctl playground create --base flexbox ubuntu-base \
  -f ./iximiuz/manifests/ubuntu-base-test.yml

Once the VM is running, connect and verify:

# Inside the VM via labctl ssh or the Labs terminal:
systemctl is-system-running       # Expected: running
systemctl status ssh              # Expected: active (running)
echo $PS1                         # Expected: colored \u@\h:\w $ prompt
cat ~/.welcome                    # Expected: banner on first login; file is deleted after

Not Valid: Plain docker run

Running this image with docker run (even with --privileged --cgroupns=host) does not produce a working systemd environment because:

  1. The image has no CMD/ENTRYPOINT - Docker falls back to the base Ubuntu shell (/bin/bash), not systemd.
  2. SSH host keys are intentionally absent - sshd will not start without them.
  3. The image is not designed or tested for Docker container execution.

Errors like System has not been booted with systemd as init system (PID 1) or cat: /home/ibtisam/.welcome: No such file or directory when using docker exec are expected in this context - they confirm the image is correctly built for microVM use only.


Integration and Usage

As a Base for Child Images

FROM ghcr.io/ibtisam-iq/ubuntu-24-04-rootfs:latest

Examples:

Guidelines for child Dockerfiles:

  1. Start with USER root to install services and system packages.
  2. Enable services with systemctl enable <service> during build.
  3. Place COPY welcome $HOME/.welcome after all RUN customize-bashrc.sh steps.
  4. End with USER root so the platform boots the VM with correct permissions.
  5. Set CMD ["/lib/systemd/systemd"] only if the child image also needs to run as a standalone Docker container (e.g., for docker run-based testing). The ubuntu base itself must not have this.

Optional: Direct Use as a Playground Rootfs

The base image can be booted directly by pointing a manifest's drive at it (as shown in Verification above). Typically use Dev Machine or another child image instead.