How to create a minimal container image for LXC/LXD with distrobuilder

In the previous post,

we saw how to build distrobuilder, then use it to create a LXD container image for Ubuntu. We used one of the existing configuration files for an Ubuntu container image.

In this post, we are going to see how to compose such YAML configuration files that describe how the container image will look like. The aim of this post is to deal with a minimal configuration file to create a container image for Alpine Linux. A future post will deal with a more complete configuration file.

Creating a minimal configuration for a container image

Here is the minimal configuration for a Alpine Linux container image. Note that we have omitted some parts that will make the container more useful (namespaces, etc). The containers from this container image will still work for our humble purposes.

description: My Alpine Linux
distribution: minimalalpine
release: 3.8.1

downloader: alpinelinux-http
- 0482D84022F52DF1C4E7CD43293ACD0907D9495A

manager: apk

Save this as a file with filename such as myalpine.yaml, and then build the container image. It takes a couple of seconds to build the container image. We will come back to the minimal configuration and explain in detail in the next section.

$ sudo $HOME/go/bin/distrobuilder build-lxd myalpine.yaml 
v3.8.1-27-g42946288bd []
v3.8.1-23-ga2d8d72222 []
OK: 9539 distinct packages available
Parallel mksquashfs: Using 4 processors
Creating 4.0 filesystem on /home/username/ContainerImages/minimal/rootfs.squashfs, block size 131072.
[==================================================|] 90/90 100%
Exportable Squashfs 4.0 filesystem, gzip compressed, data block size 131072
compressed data, compressed metadata, compressed fragments, compressed xattrs
duplicates are removed
Filesystem size 2093.68 Kbytes (2.04 Mbytes)
48.30% of uncompressed filesystem size (4334.32 Kbytes)
Inode table size 3010 bytes (2.94 Kbytes)
17.41% of uncompressed inode table size (17290 bytes)
Directory table size 4404 bytes (4.30 Kbytes)
54.01% of uncompressed directory table size (8154 bytes)
Number of duplicate files found 5
Number of inodes 481
Number of files 64
Number of fragments 5
Number of symbolic links 329
Number of device nodes 1
Number of fifo nodes 0
Number of socket nodes 0
Number of directories 87
Number of ids (unique uids + gids) 2
Number of uids 1
root (0)
Number of gids 2
root (0)
shadow (42)

And here is the container image. The size of the container image is about 2MB.

$ ls -l
total 2108
-rw-r--r-- 1 root root 364 Oct 10 20:30 lxd.tar.xz
-rw-rw-r-- 1 user user 287 Oct 10 20:30 myalpine.yaml
-rw-r--r-- 1 root root 2146304 Oct 10 20:30 rootfs.squashfs

Let’s import it into our LXD installation.

$ lxc image import --alias myminimal lxd.tar.xz rootfs.squashfs 
Image imported with fingerprint: ee9208767e745bb980a074006fa462f6878e763539c439e6bfa34c029cfc318b

And now launch a container from this container image.

$ lxc launch myminimal mycontainer
Creating mycontainer
Starting mycontainer

Let’s see the container running. It’s running, but did not get an IP address. That’s part of the cost-cutting in the initial minimal configuration file.

$ lxc list mycontainer
| NAME | STATE | IPV4 | IPV6 |
| mycontainer | RUNNING | | |

Let’s get a shell in the container and start doing things! First, set up the network configuration.

$ lxc exec mycontainer -- sh
~ # pwd
~ # cat /etc/network/interfaces
cat: can't open '/etc/network/interfaces': No such file or directory
~ # echo "auto eth0" > /etc/network/interfaces
~ # echo "iface eth0 inet dhcp" >> /etc/network/interfaces

Then, get an IP address using DHCP.

~ # ifup eth0
udhcpc: started, v1.28.4
udhcpc: sending discover
udhcpc: sending discover
udhcpc: sending select for
udhcpc: lease of obtained, lease time 3600

We got a lease, but for some reason the network was not configured. Both ifconfig and route showed no configuration. So, we complete the network configuration manually. And it works, we have access to the Internet!

~ # ifconfig eth0 up
~ # route add -net default gw
~ # ping -c 1
PING ( 56 data bytes
64 bytes from seq=0 ttl=120 time=17.451 ms
--- ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 17.451/17.451/17.451 ms
~ # exit

Let’s clear up and start studying the configuration file. We force-delete the container, and then delete the container image.

$ lxc delete --force mycontainer
$ lxc image delete myminimal

Understanding the configuration file of a container image

Here is again the container file for a minimal Alpine container image. It has three sections,

  1. image, with information about the image. We can put anything for the description and distribution name. The release version, though, should exist.
  2. source, which describes where to get the image, ISO or packages of the distribution. The downloader is a plugin in distrobuilder that knows how to get the appropriate files, as long as it knows the URL and the release version. The url is the URL prefix of the location with the files. keys and keyserver are used to verify digitally the authenticity of the files.
  3. packages, which indicates the plugin that knows how to deal with the specific package manager of the distribution. In general, you can also indicate here which additional packages to install, which to remove and which to update.
description: My Alpine Linux
distribution: minimalalpine
release: 3.8.1

downloader: alpinelinux-http
- 0482D84022F52DF1C4E7CD43293ACD0907D9495A

manager: apk

The downloader and url go hand in hand. The URL is the prefix for the repository that the downloader will use to get the necessary files.

The keys are necessary to verify the authenticity of the files. The keyserver is used to download the actual public keys of the IDs that were specified in the keys. You could very well not specify a keyserver, and distrobuilder would request the keys from the root PGP servers. However, those servers are often overloaded and the attempt can easily fail. It happened to me several times so that I explicitly use now the Ubuntu keyserver.


We have seen how to use a minimal configuration file for an Alpine container image. In future posts, we are going to see how to create more complete configuration files.

Permanent link to this article:

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: