LXC Profile

A tour of my LXC Homelab profile

Preface

LXD provides the facility to store and apply "profiles". A profile is a yaml structure which includes cloud-config which can be parsed and implemented by cloud-init. cloud-config specifies how to create an instance by allowing us to set such things as hostname, timezone, packages to install and custom actions to perform. There are a great many configuration options; I've added a link to the docs in the Resources.

I created a profile called Homelab which I used for my new instances, wherever possible. It enables me to do some very cool things very quickly.

Overview

I'll provide a summary of what my profile does.

#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
timezone: Etc/UTC
apt:
  sources_list: |
    Types: deb
    URIs: http://_gateway.lxd:801/ubuntu/
    Suites: noble noble-updates noble-backports
    Components: main universe restricted multiverse
    Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
packages:
- apt:
  - curl
  - htop
  - nginx
  - rsync
  - rsyslog
  - tmux
  - tree
  - vim-gtk3
ubuntu_pro:
  token: MY-TOKEN # Your token here
write_files:
- content: |
    module(load="imjournal")
    *.* action(type="omfwd" target="log-01.lxd" port="514" protocol="tcp" action.resumeRetryCount="100" queue.type="linkedList" queue.size="10000")
  path: /etc/rsyslog.d/99-shipper.conf
  append: false
- content: |
    set tabstop=4
    set shiftwidth=4
    set expandtab
    set ruler
    set number relativenumber
    set is hls
    set title
    set nospell
    set nocompatible
    set list
    set listchars=eol:$,tab:>-,trail:~,extends:>,precedes:<
    set ttymouse=xterm2
    set mouse=a
    cmap w!! w !sudo tee % >/dev/null
    set nowrap
    set breakindent
    set scrolloff=10
    set mouse=a
    set mouse=a
    set ttymouse=sgr
  path: /etc/skel/.vimrc
  append: false
  permissions: 0600
- content: |
    export EDITOR=vim
    export VISUAL=vim
    function set-pane-name() {
      printf '\033]2;%s\033\\' "[host: $(hostname)]"
    }
  path: /etc/skel/.bashrc
  permissions: 0600
  append: true
- content: |
    \pset pager on
    \set ECHO_HIDDEN on
    \pset null '[NULL]'
    \timing on
    \set fullexplain EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
  path: /etc/skel/.psqlrc
  permissions: 0600
  append: true
- content: |
    bind-key -n C-a send-prefix
    set -g status-bg lightblue
    set -g status-fg darkblue
    bind-key -n C-a send-prefix
    set -g status-bg lightblue
    set -g status-fg darkblue

    set -g default-terminal screen-256color

    set -g default-shell /bin/bash
    set -g history-limit 100000

    # Borders
    set -g pane-active-border-style bg=default,fg=lightblue
    set -g pane-border-style fg=pink
    set -g pane-border-status
    set -g pane-border-format '#{pane_title}'

    set -g status off
    set -g mouse on
  path: /etc/skel/.tmux.conf
  permissions: 0640
  append: true
final_message: |
  Setup complete!

You will notice that I do not, by default, create users and upload SSH keys. I tend to rely on the default ubuntu user and not to generate new ones or to upload SSH keys. I will however review this in the near future as I have plans!

Is that it?

No, actually. The cloud-config is only one part of the profile. More specifically, it is the config.user.vendor-data part. vendor-data losely translates from nerd-speak to "The stuff the organization that spawns the instance will set".

Here's a broader overview of the profile, minus the parts already presented.

name: Homelab
description: LXD profile to set up a variety of useful facilities
config:
  user.vendor-data: |
    #cloud-config
    # ... you've seen this part ...
devices:
  eth0:
    name: eth0
    network: lxdbr0
    type: nic
  root:
    path: /
    pool: zfs-lxd
    type: disk

config

There is a myriad of other config options which can be set, including config.user.user-data which losely translates to "The stuff the administrator spawning the instance will set". My apt-sources archive could potentially be moved into a separate yaml file and supplied at init/ launch time as user-data.

If we wanted to create a profile used to spin up an instance used to host docker/ podman, we could add config equivilent to the commands instructed by setup guide.

lxc config set docker-instance \
    security.nesting=true \
    security.syscalls.intercept.mknod=true \
    security.syscalls.intercept.setxattr=true

as

config:
  security.nesting: "true"
  security.syscalls.intercept.mknod=true
  security.syscalls.intercept.setxattr=true

Devices

The profile can also contain configuration for devices such as networking and storage. I use the LXD managed bridge NIC, lxdbr0 and my ZFS storage pool. The Instance Options documentation notes more of the possibilities.

Snapcraft

LXD is used to spawn containers in which to build snaps. It's a great way to create an environment and separate it from one's system. snapcraft crates a LXD project of the same name which is home to the container(s). The implication is that there is an associate profile therefore we can set up a custom profile with which to build snaps.

Round-up

Profiles are convenient and they are powerful. This was only a quick post to demonstrate how I tend to use LXD profiles and possibly encourage others to use them. If you use LXD but not profiles, I recommend you read the docs and see if they would improve your quality of life when spinning up instances.

Resources