Skip to content

Knowledge

  • systemd-run is needed to run lxc.
  • lxc is a glorified chroot. It uses cgroups like almost all container solutions.
  • UID is user id and GID is group id.
  • To run a lxc container you need to delegate cpu, io, memory and pids. By default a user can only delegate memory and pids.
  • debian uses systemd-networkd
  • firefox requires systemd to start the container.
  • If you attach with a clear environment the terminal may behave funky, export TERM=xterm-256color to fix it.

Example

user@host ~> cat /proc/self/cgroup                                                                                                                                              1
0::/user.slice/user-1000.slice/session-1.scope

user@host ~> systemd-run --user --scope --collect --shell                                                                                                                       1
Running scope as unit: run-r7f23525aef1e46c0aa667f4b2b2a5705.scope

user@host ~> cat /proc/self/cgroup
0::/user.slice/user-1000.slice/user@1000.service/app.slice/run-r7f23525aef1e46c0aa667f4b2b2a5705.scope

user@host ~> lxc-create -t download -n penguin -- --dist debian --release bullseye --arch amd64
Using image from local cache
Unpacking the rootfs

---
You just created a Debian bullseye amd64 (20220603_05:24) container.

To enable SSH, run: apt install openssh-server
No default root or user password are set by LXC.

user@host ~> lxc-ls --fancy
NAME    STATE   AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0         -      -    -    true
spyder  STOPPED 0         -      -    -    true
penguin STOPPED 0         -      -    -    true

user@host ~> lxc-start penguin

user@host ~> lxc-attach penguin --clear-env

root@penguin:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
[...]

user@host ~> lxc-ls --fancy
NAME    STATE   AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0         -      -    -    true
spyder  STOPPED 0         -      -    -    true
penguin RUNNING 0         -      -    -    true

user@host ~> lxc-stop penguin

user@host ~> lxc-destroy penguin

user@host ~> lxc-ls --fancy
NAME    STATE   AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0         -      -    -    true
spyder  STOPPED 0         -      -    -    true

If you do not execute systemd-run you will get an error like the following:

user@host ~> lxc-start firefox -F
Failed to mount cgroup at /sys/fs/cgroup/systemd: Operation not permitted
[!!!!!!] Failed to mount API filesystems.
Exiting PID 1...

Basic commands

lxc

Common options

  • --logfile|-o Output to an alternate log FILE.
  • --logpriority|-l Set log priority to LEVEL. FATAL, ALERT, CRIT, WARN, ERROR (def), NOTICE, INFO, DEBUG, TRACE.
  • --lxcpath|-P Use an alternate container path.

lxc-ls

lxc-ls [-1] [--active] [--frozen] [--running] [--stopped] [--defined] [-f] [-F format] [-g groups] [--nesting=NUM] [--filter=regex]
lxc-ls --fancy
---
NAME    STATE   AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
firefox STOPPED 0         -      -    -    true
gui     STOPPED 0         -      -    -    true

lxc-start

lxc-start {-n name} [-f config_file] [-c console_device] [-L console_logfile] [-d] [-F] [-p pid_file] [-s KEY=VAL] [-C]
          [--share-[net|ipc|uts] name|pid] [command]
lxc-start --foreground --name firefox --logfile=/tmp/test/log --logpriority=DEBUG
  • --foreground|-F Run the container in the foreground. Shows errors and console output.
  • --name|-n (optional) Name of the container.
  • --daemon|-d Run the container as a daemon without a TTY. It does not show errors.

lxc-attach

lxc-attach {-n, --name name} [-f, --rcfile config_file] [-a, --arch arch] [-e, --elevated-privileges privileges]
           [-s, --namespaces namespaces] [-R, --remount-sys-proc] [--keep-env] [--clear-env]
           [-v, --set-var variable] [--keep-var variable] [-u, --uid uid] [-g, --gid gid] [-- command]
lxc-attach -n firefox --clear-env
lxc-attach -n firefox --clear-env --uid 1000 -- firefox
  • --uid|-u Executes the command with user ID uid inside the container.
  • --gid|--g Executes the command with group ID gid inside the container.
  • --clear-env

lxc-create

lxc-create {-n name} [-f config_file] {-t template} [-B backingstore] [-- template-options]
lxc-create penguin -t download
lxc-create -t download -n firefox -- --dist debian --release bullseye --arch amd64
  • Show all images with lxc-create -t download -- --list
  • The templates are located in /usr/share/lxc/templates.

lxc-console

lxc-console {-n name} [-e escape character] [-t ttynum]
lxc-console -n test

Ctrl+a q to exit.

lxc-info

lxc-info {-n name} [-c KEY] [-s] [-p] [-i] [-S] [-H]
lxc-info firefox

lxc-stop

lxc-stop {-n name} [-W] [-r] [-t timeout] [-k] [--nokill] [--nolock]
lxc-stop firefox
  • --reboot|-r Request a reboot of the container.
  • --kill|-k Rather than requesting a clean shutdown of the container, explicitly kill all tasks in the container.
  • --nokill Only request a clean shutdown, do not kill the container tasks if the clean shutdown fails.

lxc-destroy

lxc-destroy {-n name} [-f] [-s]
lxc-destroy -n test

lxc-checkconfig

lxc-checkconfig

Check the current kernel for lxc support

This indicates there is not support for cgroups v1.

[...]
Cgroup v1 systemd controller: missing
Cgroup v1 freezer controller: missing
Cgroup ns_cgroup: required
[...]

lxc-checkpoint

lxc-checkpoint {-n name} {-D PATH} [-r] [-s] [-v] [-d] [-F]

systemd

systemd-run

systemd-run --user --scope --collect --shell
systemd-run --user --scope --collect lxc-start container
systemd-run --unit=myshell --user --scope -p "Delegate=yes" lxc-start --name penguin --foreground
systemd-run --unit=myshell1 --user --property=AllowedCPUs=0,1 -p TasksMax=100 -p Delegate=yes lxc-start -n penguin
  • --user Talk to the service manager of the calling user, rather than the service manager of the system.
  • --unit Use this unit name instead of an automatically generated one.
  • --scope Create a transient .scope unit instead of the default transient .service unit
  • --collect|-G Unload the transient unit after it completed, even if it failed.
  • --shell requests an interactive shell in the current working directory
  • --property|-p Sets a property on the scope or service unit that is created.
  • "Delegate=yes" allow delegation of everything
  • "Delegate=cpu cpuset io memory pids" allow delegation of cpu, cpuset, io, memory and pids.

For more info check man systemd-run and man systemd.resource-control.

Show current systemd scope.

cat /proc/self/cgroup
---
0::/user.slice/user-1000.slice/user@1000.service/app.slice/run-r463d33efe17540179ac04d01f657faaa.scope

lxc config

Defaults

~/.config/lxc/defaults.conf
---

````

Network

/etc/lxc/lxc-usernet







---

# Enable cpu, cpuset and io delegation

By default, a non-root user can only get *memory controller* and *pids controller* to be delegated.

See the currently allowed delegations.

cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers

memory pids


To allow delegation of other controllers such as *cpu*, *cpuset*, and *io*, run the following commands:

sudo mkdir -p /etc/systemd/system/user@.service.d cat <<EOF | sudo tee /etc/systemd/system/user@.service.d/delegate.conf [Service] Delegate=cpu cpuset io memory pids EOF sudo systemctl daemon-reload


Delegating *cpuset* is recommended as well as *cpu*.
Delegating *cpuset* requires *systemd* 244 or later.

After changing the *systemd* configuration, you need to re-login or reboot the host.
Rebooting the host is recommended.






---

# Privileged containers as root (default)

*UID* `0` inside the container is *UID* `0` outside the container.





---

# GUI without mapping the user

## Configure subuid & subgid

Authorize your *UIDs* to map ranges of *UIDs* from its namespace into child namespaces.

You can write only `user:100000:100000` but I wanted to do it differently as I have a different setup.

/etc/subuid

root:1000000:65536 lxd:1065536:65536 user:1131072:65536


/etc/subgid

root:1000000:65536 lxd:1065536:65536 user:1131072:65536


`user:1131072:65536` means that user `users` can map `65536` UIDs from `1131072`, so from `1131072` to `1196608`.

You need to reboot to apply the changes.

Some people only map `65530` but that will brake the system as user `nobody` uses `65534` and it is needed.

## Allow the user to map network devices

Allow user *user* to create up to 30 *veth* (virtual ethernet) devices connected to the *virbr2* network bridge.

/etc/lxc/lxc-usernet

yu veth virbr2 30


## Change the default configuration for the containers

~/.config/lxc/default.conf

lxc.net.0.type = veth lxc.net.0.link = virbr2 lxc.net.0.flags = up lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx lxc.idmap = u 0 1131072 65536 lxc.idmap = g 0 1131072 65536


## Configure the host

### pulseaudio

Configure *pulseaudio* to create a socket at `/run/user/1000/pulse-socket`

/etc/pulse/default.pa load-module module-native-protocol-unix auth-anonymous=1 socket=/run/user/1000/pulse-socket


Restart *pulseaudio*.

rm -rf /tmp/pulse /run/user/1000/pulse ~/.pulse* ~/.config/pulse pulseaudio -k pulseaudio --start


### Wayland

Nothing, it already creates the socket at `/run/user/1000/wayland-1`.

### GPU

Get the character

ls -la /dev/dri/ 1

drwxr-xr-x 3 root root 100 Jun 17 11:21 . drwxr-xr-x 20 root root 4460 Jun 17 15:37 .. drwxr-xr-x 2 root root 80 Jun 17 11:21 by-path crw-rw----+ 1 root video 226, 0 Jun 17 11:21 card0 crw-rw-rw- 1 root render 226, 128 Jun 17 11:21 renderD128


`card0` and `rederD128` only do *3D* acceleration, they do not output video.
`card1` does output video but I do not have it.


### ACLs

Allow the UID `1131072` (`0` inside the container) access to mount (`--x`) `/run/user/1000`
and UID `1132072` (`1000` inside the container) to write (`rwx`) to *wayland* and *pulseaudio*.

setfacl -m u:1131072:--x /run/user/1000 setfacl -m u:1132072:rwx /run/user/1000/wayland-1 setfacl -m u:1132072:rwx /run/user/1000/pulse-socket


## Create and configure the container

Create the container

lxc-create -t download -n firefox -- --dist debian --release bullseye --arch amd64


Map the required sockets into the container. The last three lines are only needed for *3D* rendering.

~/.local/share/lxc/firefox/config

lxc.mount.entry = /run/user/1000/pulse-socket home/user/1000/pulse-socket none bind,create=file,rw 0 0 lxc.mount.entry = /run/user/1000/wayland-1 home/user/1000/wayland-1 none bind,create=file,rw 0 0 lxc.mount.entry = /dev/dri/renderD128 dev/dri/renderD128 none bind,create=file,rw 0 0 lxc.cgroup2.devices.allow = c 226:128 rwm lxc.cgroup2.devices.allow = c 226:0 rwm


Start the container and attach

lxc-start firefox lxc-attach firefox --clear-env


## Set up the container

Fix the terminal.

export TERM=xterm-256color


Install dependencies.

apt update apt install -y xwayland weston x11-apps mesa-utils pulseaudio firefox-esr


Configure *pulseaudio*.

/etc/pulse/client.conf

default-server = unix:/home/user/1000/pulse-socket


Create new user.

useradd -u 1000 -m user echo "user:user" | chpasswd passwd -u user


As the folder `/home/user` already exists no new files are created so copy the `skel` directory into the user's home.

cp /etc/skel/.bash_logout /home/user/.bash_logout cp /etc/skel/.bashrc /home/user/.bashrc cp /etc/skel/.profile /home/user/.profile


Configure required environment variables.

/home/user/.bashrc

export TERM=xterm-256color export XDG_RUNTIME_DIR=/home/user/1000 export WAYLAND_DISPLAY=wayland-1 export QT_QPA_PLATFORM=wayland export DISPLAY=:0 export MOZ_ENABLE_WAYLAND=1


Make sure all files are owned by the user.

chown user:user /home/user chmod 0750 /home/user chown user:user /home/user/1000


Start the *GUI* app.

su user -l -c firefox ```


Sources