Back To Where It All Started - Let’s Build an OpenShift Home Lab

15 minute read

Happy 2023! I hope that your year is starting out on a positive trajectory. Yes, I realize that it’s already March… but, Happy New Year regardless.

I started this blog on August 1, 2021 with a post about building a Kubernetes home lab with the community supported distribution of OKD which shares its codebase with Red Hat OpenShift.

From the limited usage data that I am able to get from Google about my Blog, it appears that the most popular section of this site is the post about building a single node OpenShift cluster. So… In this post, I am going to revisit the original purpose of this blog by showing you how to build a simplified version of my OpenShift home lab. In this post you will build a single node OpenShift cluster (SNO).

This lab is intended to be very flexible so that you can reconfigure it at will. It will also grow as you are able to add additional hardware.

There are three motivations driving this re-write of the home lab project:

  1. The GL.iNet edge routers that I originally built the network around have been discontinued.

    So, it’s time for an upgrade.

  2. A disconnected network setup with multiple routers and firewalls adds complexity to the lab that might put it beyond the reach of someone just getting started.

    I am refactoring the lab documentation to allow you to start with the simplest possible setup. One router, and one Intel server.

  3. Startup cost for my original setup is higher because of the need for two routers and a Raspberry Pi.

    By eliminating the disconnected install, you save around $250 in the initial startup cost.

So, here is a home lab that you can build with one Intel server and a travel router.

Required Equipment

  1. Router - These instructions are specifically crafted for the GL-iNet GL-AXT1800 travel router. Although, if you hack the scripts you should be able to use any OpenWRT based router that has enough CPU and RAM.

  2. SD Flash Drive - You will need at least 64GB on a microSDXC flash drive:

    I’m using this one, which is pretty affordable, but also fast: 128 GB SD Card

  3. An Intel or AMD x64-64 based server - I am using an Intel NUC10i7FNK. I am really a fan of the NUC form factor computers because of their compact size.

    You can use any machine that you like, as long as it has the following characteristics:

    • At least 4 cores which support SMT (aka Hyper Thread) - You need at least 8 vCPUs

      The NUC10i7FNK has 6 cores, which is great for Kubernetes. However, they are getting a little older now and are harder to find.

      I will eventually start looking at the NUC 12 line. Check out the NUC12WSKi5, you’ll get 12 cores with 16 threads (vCPUS)! Even the entry level will give you 12 vCPUs!

    • Minimum of 32GB of RAM for Single Node Openshift or 64GB of RAM for a full three node cluster

      I highly encourage you to go with 64GB of RAM. The cost difference is not that much and the benefits of more RAM are huge.

    • 1TB of SSD - either SATA or NVMe. Don’t use spinning disks. They are too slow.

    You should be able to get a really nice lab setup for under $1,000 USD.

Install the labcli utilities for the Lab

I have created a companion project for this blog. It contains all of the shell functions that I use to ease the task of building and tearing down infrastructure in my lab.

  1. Create a directory for all of your lab manifests and utilities:

    mkdir -p ${HOME}/okd-lab/bin
  2. Create a temporary working directory:

    WORK_DIR=$(mktemp -d)
  3. Clone the git repository that I have created with helper scripts:

    git clone ${WORK_DIR}
  4. Copy the helper scripts to ${HOME}/okd-lab:

    cp ${WORK_DIR}/bin/* ${HOME}/okd-lab/bin
    chmod 700 ${HOME}/okd-lab/bin/*
  5. Copy the lab configuration example files to ${HOME}/okd-lab/lab-config/examples

    mkdir -p ${HOME}/okd-lab/lab-config/cluster-configs
    cp -r ${WORK_DIR}/examples ${HOME}/okd-lab/lab-config
  6. Copy the example files for single node OpenShift

    cp ${HOME}/okd-lab/lab-config/examples/basic-lab-kvm-sno.yaml ${HOME}/okd-lab/lab-config
    cp ${HOME}/okd-lab/lab-config/examples/cluster-configs/sno-kvm-no-pi.yaml ${HOME}/okd-lab/lab-config/cluster-configs
  7. Create a symbolic link to use the config file for a single node OpenShift cluster.

    ln -s ${HOME}/okd-lab/lab-config/basic-lab-kvm-sno.yaml ${HOME}/okd-lab/lab-config/lab.yaml
  8. Remove the temporary directory

    rm -rf ${WORK_DIR}
  9. Add the following to your shell environment:

    Your default shell will be something like bash or zsh. Although you might have changed it.

    You need to add the following line to the appropriate shell file in your home directory: .bashrc, or .zshrc, etc…


    echo ". ${HOME}/okd-lab/bin/" >> ~/.bashrc


    echo ". ${HOME}/okd-lab/bin/" >> ~/.zshrc

    Note: Take a look at the file ${HOME}/okd-lab/bin/ It will set variables in your shell when you log in, so make sure you are comfortable with what it is setting. If you don’t want to add it to your shell automatically, the you will need to execute . ${HOME}/okd-lab/bin/ before running any lab commands.

    It’s always a good practice to look at what a downloaded script is doing, since it is running with your logged in privileges… I know that you NEVER run one of those; curl some URL | bash… without looking at the file first… right?

  10. Log off and back on to set the variables.

Review the configuration

I’m being intentionally prescriptive here to help ensure success the first time you try this. I have created a lab configuration for you based on the assumption that you have the minimal equipment for your first lab.

  1. Your lab domain will be:


  2. Your lab network will be:

  3. These settings are in: ${HOME}/okd-lab/lab-config/lab.yaml

    domain: my.awesome.lab
    centos-mirror: rsync://
    sub-domain-configs: []
      - name: dev
        cluster-config-file: sno-kvm-no-pi.yaml
        domain: edge
  4. The configuration file for your OpenShift cluster is in: `${HOME}/okd-lab/lab-config/cluster-configs/sno-kvm-no-pi.yaml

      name: okd4-sno
      butane-version: v0.16.0
      butane-spec-version: 1.4.0
      butane-variant: fcos
      metal: false
        memory: 12288
        cpu: 4
        root-vol: 50
      kvm-host: kvm-host01
      metal: false
        memory: 61440
        cpu: 12
        root-vol: 800
        - kvm-host: kvm-host01
      - host-name: kvm-host01
        mac-addr: "YOUR_HOST_MAC_HERE"
          disk1: nvme0n1
          disk2: NA

    Note: You will need to replace YOUR_HOST_MAC_HERE with the MAC address of your server. We’ll do that later when we get ready to install OpenShift.

Note: If you want different network settings, or a different domain, change these two files accordingly. However, I highly encourage you to deploy the lab at least once with the prescriptive configuration. This will get you familiar with how I’ve set it up. Trust me, it’s really easy to tear it down and rebuild it.

Install yq

We will need the yq utility for YAML file manipulation:

  • MacOS:

    brew install yq
  • Linux:

    mkdir ${OKD_LAB_PATH}/yq-tmp
    YQ_VER=$(basename $(curl -Ls -o /dev/null -w %{url_effective}
    wget -O ${OKD_LAB_PATH}/yq-tmp/yq.tar.gz${YQ_VER}/yq_linux_amd64.tar.gz
    tar -xzf ${OKD_LAB_PATH}/yq-tmp/yq.tar.gz -C ${OKD_LAB_PATH}/yq-tmp
    cp ${OKD_LAB_PATH}/yq-tmp/yq_linux_amd64 ${OKD_LAB_PATH}/bin/yq
    chmod 700 ${OKD_LAB_PATH}/bin/yq

Create an SSH Key Pair

  1. If you don’t have an SSH key pair configured on your workstation, then create one now:

    ssh-keygen -t rsa -b 4096 -N "" -f ${HOME}/.ssh/id_rsa
  2. Copy the SSH public key to the Lab configuration folder:

    cp ~/.ssh/ ${OKD_LAB_PATH}/
  3. Now, you are ready to set up your lab network:

Configure the Lab Network

Note: If at any time you need to reset the router, or any of the below commands fail and need to be rerun, do this:

Hold the highlighted button for about 10 seconds. When you first press the button, the left most LED will start to slowly blink. After about 3-4 seconds it will blink a bit faster. After about 9-10 seconds it will blink really fast. At this point, let go of the button. Your router will factory reset itself. The router pictured here in a GL-iNet AR750S, however most GL-iNet routers have the same button configuration.

We are going to hang your lab network off of your home network. We’re doing this for a couple of reasons.

  1. It keeps the lab portable. By using an existing network as our internet gateway, we can disconnect and move to another network at any time.

  2. It keeps the peace in your household. Our network will be serving DHCP, DNS, and a host of other services. By isolating it from your general use network, we avoid any interference with other activities.

Set Up The Router

  1. Insert the SD Card into the slot on your router.

    Don’t worry about the format of the card. The lab scripts will format it.

    Note: Do not use an SD Card with data on it that you want to keep. The card will be formatted during initialization.

  2. Connect to your lab router:

    • Power it on and connect to it from your workstation.
    • With the GL-AXT1800 you can connect to the WiFi. The initial SSID and passphrase are on the back of the router. Otherwise, connect from your workstation with a network cable.
    • Ensure that you can ping the router: ping
  3. Enable password-less SSH to the router:

    cat ${OKD_LAB_PATH}/ | ssh root@ "cat >> /etc/dropbear/authorized_keys"
  4. Set a root password on your router:

    Point your browser at

    You should see the welcome page for the router. Select you preferred language and set a password for the router.

  5. Connect your router to the internet:

    The GL-AXT1800 can be connected to your home network via cable, or wireless repeater. You’ll get the fastest speeds with a hard wire to your home router, but that also limits the placement of your lab. Repeater mode on the GL-AXT1800 is surprisingly fast. That is how I am using it.

    1. Set up Repeater Mode:

      Return to

    2. Select Connect from the Repeater configuration menu in the lower left of the main page.

    3. Choose the wireless network that you want to connect to, and enter the appropriate passphrase.

    4. Re-connect to your router’s wireless network.

  6. Set your shell environment for managing the lab:

    labctx dev
  7. Initialize the router configuration:

    labcli --router -i -e

    When the configuration is complete, the router will reboot.

  8. Wait for the router to reboot, and then reconnect to your new lab network.

  9. Make sure that you have internet connectivity through the router:

  10. Finish configuring the router:

    labcli --router -s -e -f

    When the configuration is complete, the router will reboot again.

  11. Wait for the router to reboot, and then reconnect to your new lab network.

    Note: When you update the firmware on your router, you will need to run all of the above steps again. However, to preserve the data on the SD Card, leave out the -f option. By doing so, you will not lose your DNS configuration or the CentOS Stream repo synch.

  12. Verify that DNS is working properly:


Prepare to Install CentOS Stream on Your KVM Host

  1. Create a mirror of the CentOS Stream repo on your router:

    ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@ "nohup /root/bin/ &"

    This process will run in the background and will take quite a while to complete. Depending on network speed, this could take an hour or more.

    You can check on the progress of the CentOS Stream synch by following the log at /usr/local/MirrorSync.log

  2. Once the CentOS Stream repository has finished synching, go on to the next section to set up your KVM host.

Set Up KVM Host

  1. Create a default root password for your KVM hosts:

    labcli --kvm-pwd
  2. Read the MAC address off of the bottom of the NUC and add it to the cluster config file:

    Edit ${HOME}/okd-lab/lab-config/domain-configs/sno-kvm-no-pi.yaml and replace YOUR_HOST_MAC_HERE with the MAC address of your NUC.

    Note: Use lower case letters in the MAC.

  3. You need to know whether you have NVME or SATA SSDs in the NUC.

    1. If you have an NVME drive installed in the NUC, you do not need to modify anything.

    2. If you have SATA M.2 drive instead of NVME then edit: ${OKD_LAB_PATH}/lab-config/domain-configs/sno-kvm-no-pi.yaml, and replace nvme0n1 with sda.

    3. If you have more than one drive installed and you want to use all of them for storage, then edit: ${OKD_LAB_PATH}/lab-config/domain-configs/sno-kvm-no-pi.yaml, and replace disk2: NA with disk2: nvme0n2 or disk2: sdb as appropriate

Once you have completed the configuration file changes, Deploy the KVM hosts:

  1. Prepare for the CentOS Stream install:

    labcli --deploy -k

    This command will configure the iPXE and kickstart files for you as well as create the appropriate DNS records.

  2. We are now ready to plug in the NUC and boot it up.

    Note: This is the point at which you might have to attach a keyboard and monitor to your NUC. We need to ensure that the BIOS is set up to attempt a Network Boot with UEFI, not legacy. You also need to ensure that Secure Boot is disabled in the BIOS since we are not explicitly trusting the boot images.

    Also, Take this opportunity to apply the latest BIOS to your NUC.__ You won’t need the keyboard or mouse again, until it’s time for another BIOS update… Eventually we’ll figure out how to push those from the OS too.

  3. Make sure that the KVM host is connected to your network and power it on.

    At this point, it should PXE boot off of the router, and start an unattended install of CentOS Stream.

    Attach a monitor and keyboard if you want to watch.

    1. The host will power on and find no bootable OS
    2. The host will attempt a network boot by requesting a DHCP address and PXE boot info
      • The DHCP server will issue an IP address and direct the host to the PXE boot file on the TFTP boot server
    3. The host will retrieve the boot.ipxe file from the TFTP boot server
    4. The boot.ipxe script will then retrieve an iPXE script name from the MAC address of the host.
    5. The host will begin booting:
      1. The host will retrieve the vmlinuz, and initrd files from the HTTP install server
      2. The host will load the kernel and init-ram
      3. The host will retrieve the kickstart file or ignition config file depending on the install type.
    6. The host should now begin an unattended install.

We are now ready to deploy our Single Node OpenShift cluster

  1. Pull the latest release binaries for OKD:

    labcli --latest
  2. Deploy the configuration in preparation for the install:

    labcli --deploy -c

    This command does a lot of work for you.

    • It creates the OpenShift install manifests.
    • It uses the butane cli to inject custom configurations into the ignition configs for the cluster nodes.
    • It creates the appropriate DNS entries and network configuration.
    • It prepares the iPXE boot configuration for each cluster node.
  3. Start the bootstrap node:

    labcli --start -b
  4. Start the control-plane node:

    labcli --start -m
  5. Monitor the bootstrap process:

    labcli --monitor -b

    Note: This command does not affect the install process. You can stop and restart it safely. It is just for monitoring the bootstrap.

    Also Note: It will take a while for this command to stop throwing connection errors. You are effectively waiting for the bootstrap node to install its OS and start the bootstrap process. Be patient, and don’t worry.

    If you want to watch logs for issues:

    labcli --monitor -j

    This command tails the journal log on the bootstrap node.

  6. You will see the following, when the bootstrap is complete:

    DEBUG Still waiting for the Kubernetes API: Get "": read tcp> read: connection reset by peer - error from a previous attempt: read tcp> read: connection reset by peer 
    INFO API v1.25.0-2786+eab9cc98fe4c00-dirty up     
    DEBUG Loading Install Config...                    
    DEBUG   Loading SSH Key...                         
    DEBUG   Loading Base Domain...                     
    DEBUG     Loading Platform...                      
    DEBUG   Loading Cluster Name...                    
    DEBUG     Loading Base Domain...                   
    DEBUG     Loading Platform...                      
    DEBUG   Loading Networking...                      
    DEBUG     Loading Platform...                      
    DEBUG   Loading Pull Secret...                     
    DEBUG   Loading Platform...                        
    DEBUG Using Install Config loaded from state file  
    INFO Waiting up to 30m0s (until 10:06AM) for bootstrapping to complete... 
    DEBUG Bootstrap status: complete                   
    INFO It is now safe to remove the bootstrap resources 
    DEBUG Time elapsed per stage:                      
    DEBUG Bootstrap Complete: 17m10s                   
    DEBUG                API: 4m9s                     
    INFO Time elapsed: 17m10s                         
  7. When the bootstrap process is complete, remove the bootstrap node:

    labcli --destroy -b

    This script shuts down and then deletes the Bootstrap VM. Then it removes the bootstrap entries from the DNS and network configuration.

  8. Monitor the installation process:

    labcli --monitor -i

    Note: This command does not affect to install process. You can stop and restart it safely. It is just for monitoring.

  9. Installation Complete:

    DEBUG Cluster is initialized                       
    INFO Waiting up to 10m0s for the openshift-console route to be created... 
    DEBUG Route found in openshift-console namespace: console 
    DEBUG OpenShift console route is admitted          
    INFO Install complete!                            
    INFO To access the cluster as the system:admin user when using 'oc', run 'export KUBECONFIG=/Users/yourhome/okd-lab/okd-install-dir/auth/kubeconfig' 
    INFO Access the OpenShift web-console here: 
    INFO Login to the console with user: "kubeadmin", and password: "AhnsQ-CGRqg-gHu2h-rYZw3" 
    DEBUG Time elapsed per stage:                      
    DEBUG Cluster Operators: 13m49s                    
    INFO Time elapsed: 13m49s

Post Install

  1. Post Install Cleanup:

    labcli --post
  2. Trust the cluster certificates:

    labcli --trust -c
  3. Add Users:

    Note: Make sure that the htpasswd command is installed on your system. It should be included by default on Mac OS. For Fedora, RHEL, or CentOS: dnf install httpd-tools

    Add a cluster-admin user:

    labcli --user -i -a -u=admin

    Note: You can ignore the warning: Warning: User 'admin' not found

    Add a non-privileged user:

    labcli --user -u=devuser

    Note: It will take a couple of minutes for the authentication services to restart after you create these user accounts.

    Note: This deletes the temporary kubeadmin account. Your admin user will now have cluster admin rights.

Install The Hostpath Provisioner Operator as a storage Provisioner

  1. Log into your cluster:

    oc login -u admin

    Note: Use the admin password for the user that you created above.

  2. Install the Cert Manager Operator (Dependency)

    oc create -f
  3. Wait for the operator to install:

    oc wait --for=condition=Available -n cert-manager --timeout=120s --all deployments
  4. Install the Hostpath Provisioner:

    oc create -f
    oc create -f -n hostpath-provisioner
    oc create -f -n hostpath-provisioner
  5. Create an instance of the Hostpath Provisioner:

    cat << EOF | oc apply -f -
    kind: HostPathProvisioner
      name: hostpath-provisioner
      imagePullPolicy: Always
        - name: "local"
          path: "/var/hostpathvolumes"
  6. Create a StorageClass:

    cat << EOF | oc apply -f -
    kind: StorageClass
      name: hostpath-csi
      annotations: "true"
    reclaimPolicy: Delete
    volumeBindingMode: WaitForFirstConsumer
      storagePool: local

Create a Storage Volume for the Internal Image Registry

Verify that the Hostpath Provisioner is working by creating a PersistentVolumeClaim for the OpenShift internal image registry.

  1. Create the PVC:

    cat << EOF | oc apply -f -
    apiVersion: v1
    kind: PersistentVolumeClaim
      name: registry-pvc
      namespace: openshift-image-registry
      - ReadWriteOnce
          storage: 100Gi
      storageClassName: hostpath-csi
  2. Patch the internal image registry to use the new PVC:

    oc patch cluster --type merge --patch '{"spec":{"rolloutStrategy":"Recreate","managementState":"Managed","storage":{"pvc":{"claim":"registry-pvc"}}}}'

That’s it!

Have fun with OpenShift

Next time, I’ll show you how to use this same setup to build a three node cluster with Ceph as the storage provisioner.