I have been used to have a server running under my desk for years now. It serves “Nextcloud” for internal data sharing and “GitLab CE” for hosting my internal “git” repositories. Unfortunately this year was quite a hot one in terms of temperature. I was forced to shutdown the server multiple times due to the heat in my home office. As a result I started a research in order to find a solution about how to setup a much smaller system which does not heat up the room that much. This article describes my search to get it up and running with “Alpine Linux” in “diskless mode”.


Choose the hardware

First, I considered those tiny and inexpensive ARM-based systems including a Raspberry Pi, but most of my toolchain is x86-based. That is why I gave up on the idea of using ARM-based devices and bought an “OROID H2+” made by Hardkernel with a “type 3 case”. It has an Intel Celeron J4115 as processor. To store my data, I chose a “SanDisk Solid State Disk (SSD) Ultra 3D” with 2 TB of storage. The operating system is saved on an old SSD, but any storage with more than 1 GiB is fine. Even a USB-stick should work; I use this kind of setup for other server builds with an “OROID H2+”. For builds for which I need more storage I use “3.5 inch disks” as data disks and a “type 1 case”.


Fig. 1: “ODROID H2+” in a type 3 case

Choose the operating system

I try to reduce the amount work as I only want to keep my infrastructure at home up and running. I would like to have a setup which does not make me worry about updates and which was built with security in mind. I chose “Alpine Linux” as it fulfils all my requirements and also supports a so called “diskless” mode.

This mode makes the operating system (OS) run in main memory (RAM) and reduces write accesses to the disk where the OS is stored. Normally you would run it from a physical or virtual CDROM (CD) drive. The chosen hardware platform “ODROID H2+” misses a builtin CD drive, but I have a USB-CD-drive available for installations. I was happy to read about a tool called setup-bootable which creates a permanent installation of “Alpine Linux” in diskless mode on a hard disk from their live install CD.

Requisites for readers

This article is written for people with a basic understanding in “Linux” operating systems. Writing this article I assume the reader does not have “Alpine Linux” running on her/his workstation. But I assume that you already built your “ODROID H2+” according to the official YouTube Video. I added tags to clarify which commands have to be executed on which of your systems:

  • Workstation: Your local desktop computer or laptop which you use to setup the server
  • Server: The server you’re going to install. It is controlled directly via monitor and keyboard
  • Server (SSH): The server you’re going to install. It is controlled over ssh from your workstation

To make this article easier to read, I do not prefix commands run as root with sudo. Instead, I use the following syntax for the commands in this article. But for your daily business, I definitively recommend using the sudo-command.

  • $ command : Running the command as a normal or admin user
  • # command : Running the command as root

Server setup

Prepare installation of the operating system

  • WorkstationDownload the extended ISO image and burn it onto a CD

    Download the extended ISO image from the “Alpine Linux” download site and burn it onto a CD. I use Brasero to burn images onto CDs. At the time of writing “3.12” is the current version of “Alpine Linux”.

  • ServerConnect the “ODROID H2+” to a monitor and keyboard

  • ServerConnect the “ODROID H2+” to your local network by cable

  • ServerBoot the OS from CD

    Make sure your “ODROID H2+” server starts up in UEFI mode. If you’re not sure, please check the documentation about how to configure the hardware start up routine correctly. When the installation OS is booted, please login with root. There’s no password required for the login.

  • ServerRun the “Alpine Linux” installer

    Upon having set up a working network connection you run the installer for the first time. This will generate some files including the repositories file for apk and start some very basic services like an SSH server.

    # setup-alpine

    Enter the following into the prompt of the installer.

    Keyboard layout: none (or whatever suits your preference)
    Hostname: localhost
    Network interface: eth0
    IP address: dhcp
    Manual network configuration: no
    Password: test123
    Again password: test123
    Time zone: UTC
    HTTP proxy: none
    NTP client: chrony
    Mirror: 1 (or whatever suits your preference)
    SSH server: openssh
    Setup disk: none
    Store configs: none
    Apk cache directory: none

    You can find more details in the “Alpine Linux” wiki: Creating a bootable usb stick, Installation of “Alpine Linux”, Saving changes in an diskless setup.

  • ServerFix configuration for OpenSSH server

    IMPORTANT: Make sure you install your server in a secured network.

    The following configuration can be considered as insecure, but is sufficient for a setup of the server in a secured environment. This will activate the login for root via SSH. As we configured a rather weak password, make sure to not use such a configuration for a server in a public network.

    # vi /etc/ssh/sshd_config
    PermitRootLogin yes
  • ServerRestart the SSH daemon

    To activate the new configuration, please restart the SSH daemon.

    # rc-service sshd restart
  • ServerShow the IP address of your server

    To make operations like copy and paste a bit easier, I usually try to use SSH to connect remotely to a server. Before you can proceed, you need to get the IP address of your server. Nowadays I use ip for this, but ifconfig will work just as fine as well. Make sure you run this command on your server, NOT on your local computer.

    $ ip address
    [... ]
    2: eth0
    inet 192.168.x.x
    [... ]
  • WorkstationConnect to your server via SSH from your local computer

    Please start a new shell on your local system and run the following command.

    $ ssh root@<IP address of your server>
  • Server (SSH)Update repository meta data

    apk requires information about packages it can install. To fetch this kind of information, run the following command.

    # apk update
    3.12.0 [/media/cdrom/apks]
    v3.12.0-245-gcd32bb7c9a []
    v3.12.0-245-gcd32bb7c9a []
    OK: 4849 distinct packages available

    There’s a shortcut for this as well: Run apk add -u <package> to update the package information and install the package in one go.

  • Server (SSH)Install lsblk to gather information about your storage setup

    # apk add lsblk
    $ # or
    # apk add -u lsblk

Setup storage for operating system

  • Server (SSH)Find hard disk (HDD) to install “Alpine Linux”

    We need to find out the name of the hard disk where we can install “Alpine Linux”. In this case it’s an old SSD, but as mentioned before any HDD, SSD or USB-stick is fine as long it’s big enough.

    # lsblk
    sda      8:0    0  7.5G  0 disk
  • Server (SSH)Create partitions on disks

    Run BusyBox’ fdisk to create partitions. I don’t want to go into too much detail, now. There are plenty of guides for this out there on the internet. IMPORTANT: Make sure to choose “EFI” for the partition type.

    # fdisk /dev/sda
    # fdisk -l /dev/sda
    Disk /dev/sda: 7641 MB, 8012390400 bytes, 15649200 sectors
    8280 cylinders, 30 heads, 63 sectors/track
    Units: sectors of 1 * 512 = 512 bytes
    Device  Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
    /dev/sda1    0,1,1       974,29,63           63   15649199   15649137 7641M ef EFI (FAT-12/16/32)
  • Server (SSH)Load kernel module for “VFAT” filesystem

    You need to load the vfat kernel module to be able to format the partition.

    # modprobe vfat
  • Server (SSH)Format partition for operating system

    # mkfs.vfat /dev/sda1

Install operating system

  • Server (SSH)Install “Alpine Linux” to a disk

    This command will copy all necessary files for a full “Alpine Linux” diskless installation. You might see a different directory for your source media and destination device.

    IMPORTANT: /dev/sda1 must not be mounted before you run this command.

    # setup-bootable -v /media/cdrom/ /dev/sda1
    Using /dev/sda1 as target (mounted on /media/sda1)
    Installing /dev/sda1 to alpine-extended-3.12.0 200529
    Copying /media/cdrom/boot to /media/sda1/.new/
    Copying /media/cdrom/efi to /media/sda1/.new/
    Copying /media/cdrom/apks to /media/sda1/.new/
    Copying /media/cdrom/.alpine-release to /media/sda1/.new/
    Found boot/syslinux/syslinux.cfg
    Flushing cache...
    Replacing existing files...
    Making /dev/sda1 bootable...
  • Server (SSH)Restart the server to boot into the installed OS

    Restart the server with “Alpine Linux” installed. From now on you’re modifying your setup.

    # reboot
  • ServerDisconnect the device with installation media

    Make sure you disconnect your CD drive and/or change the hardware start up routine to boot from /dev/sda.

Setup operating system

  • ServerLogin with root and no password to your server

    At this stage of work the installation on your disk is the same as on the installation CD, but you’re going to change this in the following steps.

  • ServerRun the “Alpine Linux” installer

    First we need to run the installer again.

    # setup-alpine

    This time you enter all the information you want the installer to persist. You might need to enter something different from the list given below depending on your requirements and local infrastructure.

    keyboard layout: us (or whatever suits your preference)
    variant: us-altgr-intl
    hostname: <Your Hostname>
    network interface: eth0
    IP address: dhcp
    Manual network configuration: no
    password: <Your root Password>
    again password: <Your root Password>
    time zone: Europe/Berlin (or whatever suits your preference)
    HTTP proxy: none
    NTP client: chrony
    Mirror: 1 (or whatever suits your preference)
    SSH server: openssh
    Setup disk: none
    Store configs: sda1
    Apk cache directory: /media/sda1/cache
  • ServerFix configuration for OpenSSH server temporary

    We need to change the setup of the OpenSSH server again. It’s the same procedure like before: So, make sure, that you install your server in a secured network.

    # vi /etc/ssh/sshd_config
    PermitRootLogin yes
  • ServerRestart sshd

    After you have changed the configuration, please restart “sshd”.

    # rc-service sshd restart
  • ServerShow the ip address of your server

    Please gather the ip address of your server again. This might be a different one than before.

    $ ip address
    [... ]
    2: eth0
    inet 192.168.x.x
    [... ]
  • Workstation(optional) Copy the SSH public key from your local system to your new server

    If you prefer to use public/private keys with SSH, please open a new terminal on your local system and copy your local public SSH key to the server.

    $ ssh-copy-id root@<IP address>
  • WorkstationConnect to your server via SSH from your local system

    Please connect to the server via ssh from your local system. Depending on your SSH setup, you may need to unlock your SSH key first or login with username and password.

    $ ssh root@<IP address>
  • Server (SSH)(optional) Commit changes for passwordless access for root permanently

    This steps is only required, if you ran ssh-copy-id earlier. Files in /root are not part of the regular “diskless mode” save list. These commands will save root’s authorized_keys file to the overlay file.

    # lbu add /root/.ssh/authorized_keys
  • Server (SSH)Fix configuration for OpenSSH server permanently

    # vi /etc/ssh/sshd_config

    Remove the following line.

    PermitRootLogin yes

    Add one of the following lines instead. Option 1) will allow SSH access for root via SSH public/private keys, but not with passwords and option 2) will prevent login with the root user at all.

    # /etc/ssh/sshd_config
    # 1)
    # public / private key only
    PermitRootLogin prohibit-password
    # or
    # 2)
    # no root login at all
    PermitRootLogin no
  • Server (SSH)Restart sshd

    Again, restart sshd after you saved the configuration file. Do not reboot at this point!

    # rc-service sshd restart
  • Server (SSH)(optional) Commit changes for remote access permanently

    # lbu commit -d

    You shall see an overlay file on your root disk which contains all changed files.

    $ ls /media/sda1/*.tar.gz
    /media/sda1/<Your Hostname>.apkovl.tar.gz
  • WorkstationConnect to your server via SSH from your local system via a different session

    In order to verify that the sshd is working correctly, connect to the server using a second connection. Please create a new shell on your workstation and run this command. You can close this connection after it was built up successfully.

    $ ssh root@<IP address>
  • Server (SSH)Make sure all edge repositories are disabled

    The edge repositories contain bleeding edge versions of known packages. Using these might break your setup. This is why you should disable them for your own sake.

    # vi /etc/apk/repositories
  • Server (SSH)Commit configuration made so far

    # lbu commit -d

Setup storage for root disk

  • Server (SSH)Add mkinitfs to update the kernel and rebuild the “modloop”-image

    # apk add mkinitfs
    (1/12) Installing lddtree (1.26-r2)
    (2/12) Installing xz-libs (5.2.5-r0)
    (3/12) Installing kmod (27-r0)
    (4/12) Installing kmod-openrc (27-r0)
    (5/12) Installing libblkid (2.35.2-r0)
    (6/12) Installing argon2-libs (20190702-r1)
    (7/12) Installing device-mapper-libs (2.02.186-r1)
    (8/12) Installing json-c (0.14-r1)
    (9/12) Installing libuuid (2.35.2-r0)
    (10/12) Installing cryptsetup-libs (2.3.2-r0)
    (11/12) Installing kmod-libs (27-r0)
    (12/12) Installing mkinitfs (3.4.5-r3)
    Executing busybox-1.31.1-r16.trigger
    OK: 46 MiB in 47 packages
  • Server (SSH)Add blkid to add entries to the /etc/fstab-file

    # apk add blkid
    (1/1) Installing blkid (2.35.2-r0)
    Executing busybox-1.31.1-r16.trigger
    OK: 46 MiB in 48 packages
  • Server (SSH)Commit configuration

    The next steps might break your setup. That is why this is a good time to commit your configuration now.

    # lbu commit -d
  • Server (SSH)Make sure your root disk is writable

    # mount -o remount,rw /media/sda1/
  • Server (SSH)Update kernel and install required packages

    IMPORTANT: Make sure you’ve got plenty of RAM for this step – at least 8 GiB. If there’s not enough RAM you end up with a broken “modloop”-image.

    As we would like to use “LVM” and “XFS” we also need to install the required packages to mount the filesystems during boot.

    IMPORTANT: Add all required features as value for the -F-flag. The default configuration of mkinitfs is only used if the -F-flag is omitted. The default value for this flag is ata base cdrom ext4 keymap kms mmc raid scsi usb virtio and can be found in /etc/mkinitfs/mkinitfs.conf. If you leave out some of the basic features you might end up with a broken “modloop” image.

    # update-kernel -F "ata base cdrom lvm xfs keymap kms scsi usb" -p xfsprogs -p lvm2 /media/sda1/boot/
    Warning: extra firmware "" not found!
    Parallel mksquashfs: Using 4 processors
    Creating 4.0 filesystem on /tmp/update-kernel.BGDffp/boot/modloop-lts, block size 131072.
    [==========================================================================================================================================================\] 6459/6459 100%
    Exportable Squashfs 4.0 filesystem, xz compressed, data block size 131072
            compressed data, compressed metadata, compressed fragments,
            compressed xattrs, compressed ids
            duplicates are removed
    Filesystem size 91967.62 Kbytes (89.81 Mbytes)
            23.93% of uncompressed filesystem size (384375.24 Kbytes)
    Inode table size 44566 bytes (43.52 Kbytes)
            24.37% of uncompressed inode table size (182890 bytes)
    Directory table size 50190 bytes (49.01 Kbytes)
            41.93% of uncompressed directory table size (119703 bytes)
    Number of duplicate files found 258
    Number of inodes 5401
    Number of files 4530
    Number of fragments 1004
    Number of symbolic links  0
    Number of device nodes 0
    Number of fifo nodes 0
    Number of socket nodes 0
    Number of directories 871
    Number of ids (unique uids + gids) 3
    Number of uids 2
            root (0)
            unknown (2291)
    Number of gids 2
            root (0)
            unknown (1022)
  • Server (SSH)(optional) Repair broken “modloop”-image

    If anything went wrong in the previous step, boot “Alpine Linux” from CD and run the following commands. You might need to change the device names depending on your setup.

    # mount -o remount,rw /media/sda1/
    # update-kernel /media/sda1/boot/
  • Server (SSH)Add packages to manage “LVM” and “XFS” on your booted server

    This step adds tools to your booted server, so that you can manage your storage devices etc. after the server was booted.

    # apk add lvm2 xfsprogs
    (1/6) Installing libaio (0.3.112-r1)
    (2/6) Installing device-mapper-event-libs (2.02.186-r1)
    (3/6) Installing lvm2-libs (2.02.186-r1)
    (4/6) Installing lvm2 (2.02.186-r1)
    (5/6) Installing lvm2-openrc (2.02.186-r1)
    (6/6) Installing xfsprogs (5.6.0-r1)
    Executing busybox-1.31.1-r16.trigger
    OK: 52 MiB in 54 packages

    For “LVM” to work correctly, you need to enable the “LVM” daemon.

    # rc-update add lvm
    * service lvm added to runlevel default
  • Server (SSH)Install lsblk to gather information about your storage devices

    You need to install lsblk again. This time this is the persistent installation.

    # apk add lsblk
    (1/3) Installing libmount (2.35.2-r0)
    (2/3) Installing libsmartcols (2.35.2-r0)
    (3/3) Installing lsblk (2.35.2-r0)
    Executing busybox-1.31.1-r16.trigger
    OK: 53 MiB in 57 packages
  • Server (SSH)Find disk to setup LVM

    You use lsblk to find out the device name of your storage device.

    # lsblk
    sdb      8:16   0  1.8T  0 disk
  • Server (SSH)Prepare data disks

    Run BusyBox’ fdisk to create partitions. As before, I won’t go into too much detail. Best follow other guides for this on the internet.

    # fdisk /dev/sdb
    # fdisk -l /dev/sdb
    Disk /dev/sdb: 1863 GB, 2000398934016 bytes, 3907029168 sectors
    243201 cylinders, 255 heads, 63 sectors/track
    Units: sectors of 1 * 512 = 512 bytes
    Device  Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
    /dev/sdb1    0,1,1       1023,254,63         63 3907029167 3907029105 1863G 83 Linux
  • Server (SSH)Setup LVM

    Step one: make /dev/sdb1 a physical volume.

    # pvcreate /dev/sdb1
    Physical volume "/dev/sdb1" successfully created.

    Step two: create a volume group using that physical volume.

    # vgcreate vg_data /dev/sdb1
    Volume group "vg_data" successfully created

    Step three: the values for the storage size depend on your setup. I bought a 2 TB SSD and decided to create a disk with 32 GiB where my container images shall be stored.

    # lvcreate -L +32G vg_data -n lv_images
    Logical volume "lv_images" created.

    Step four: the rest of the disk is used by the data volume.

    # lvcreate -l 100%FREE vg_data -n lv_data
    Logical volume "lv_data" created.

    Last step: activate the created volumes.

    # vgchange -ay
    2 logical volume(s) in volume group "vg_data" now active

    This might look similar to your setup so far.

    # lsblk
    loop0                   7:0    0 89.8M  1 loop /.modloop
    sda                     8:0    0  7.5G  0 disk
    └─sda1                  8:1    0  7.5G  0 part /media/sda1
    sdb                     8:16   0  1.8T  0 disk
    └─sdb1                  8:17   0  1.8T  0 part
      ├─vg_data-lv_images 253:0    0   32G  0 lvm
      └─vg_data-lv_data   253:1    0  1.8T  0 lvm
  • Server (SSH)Create filesystem on the logical volumes

    At the beginning of this chapter you installed programs in order to install and manage “XFS” filesystems. Now you use one of these programs to setup “XFS” disks.

    First we create the filesystem for the container images.

    # mkfs.xfs /dev/mapper/vg_data-lv_images
    meta-data=/dev/mapper/vg_data-lv_images isize=512    agcount=4, agsize=2097152 blks
             =                       sectsz=512   attr=2, projid32bit=1
             =                       crc=1        finobt=1, sparse=1, rmapbt=0
             =                       reflink=1
    data     =                       bsize=4096   blocks=8388608, imaxpct=25
             =                       sunit=0      swidth=0 blks
    naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
    log      =internal log           bsize=4096   blocks=4096, version=2
             =                       sectsz=512   sunit=0 blks, lazy-count=1
    realtime =none                   extsz=4096   blocks=0, rtextents=0
    Discarding blocks...Done.

    Next we create the filesystem for the data.

    # mkfs.xfs /dev/mapper/vg_data-lv_data
    meta-data=/dev/mapper/vg_data-lv_data isize=512    agcount=4, agsize=119997440 blks
             =                       sectsz=512   attr=2, projid32bit=1
             =                       crc=1        finobt=1, sparse=1, rmapbt=0
             =                       reflink=1
    data     =                       bsize=4096   blocks=479989760, imaxpct=5
             =                       sunit=0      swidth=0 blks
    naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
    log      =internal log           bsize=4096   blocks=234370, version=2
             =                       sectsz=512   sunit=0 blks, lazy-count=1
    realtime =none                   extsz=4096   blocks=0, rtextents=0
  • Server (SSH)Add new disks to /etc/fstab

    Hint: Using UUIDs does not work and fails during boot.

    /dev/mapper/vg_data-lv_images /storage/images xfs defaults 0 2
    /dev/mapper/vg_data-lv_data /storage/data xfs defaults 0 2
  • Server (SSH)Create mount points

    # mkdir -p /storage/images
    # mkdir -p /storage/data
  • Server (SSH)Make mount points persistent across reboots

    # touch /storage/.keep /storage/images/.keep /storage/data/.keep
  • Server (SSH)Add directory to save list

    To make sure your mount points are part of the overlay image, please run the following commands.

    # lbu add /storage/
  • Server (SSH)Commit changes to disk

    After that, please commit all changes to disk.

    # lbu commit -d
  • Server (SSH)Mount disks to verify setup

    Now you can use the mount command to verify all disks can be mounted.

    # mount -a

    Your setup should look similar.

    # mount | grep storage
    /dev/mapper/vg_data-lv_images on /storage/images type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
    /dev/mapper/vg_data-lv_data on /storage/data type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
  • Server (SSH)Reboot

    At the very end, please reboot your computer to see if your setup works correctly. You should see no errors on mounting the disks in the start up logs. After reboot you should see a login prompt.

    # reboot


Now you should have a working server installation on your “ODROID H2+”. This installation can be used to setup a small container host to run your “Nextcloud” and “GitLab” containers. But this topic has enough material for another article. For now, I hope you enjoyed reading this one.


If you found a mistake in this article or would like to contribute some content to it, please file an issue in this Git Repository


The contents of this article are put together to the best of the authors' knowledge, but it cannot be guaranteed that it's always accurate in any environment. It is up to the reader to make sure that all information found in this article, does not do any damage to the reader's working environment or wherever this information is applied to. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, arising from, out of or in connection with this article. Please also note the information given on the Licences' page.