<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Peter W</title>
    <description>The latest articles on Forem by Peter W (@techieshark).</description>
    <link>https://forem.com/techieshark</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3629542%2F2cd42984-420a-499c-9ac4-07329daa1f10.jpeg</url>
      <title>Forem: Peter W</title>
      <link>https://forem.com/techieshark</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/techieshark"/>
    <language>en</language>
    <item>
      <title>The US Tech Force just launched. It's title font is Canadian. And it gets worse.</title>
      <dc:creator>Peter W</dc:creator>
      <pubDate>Mon, 22 Dec 2025 02:38:47 +0000</pubDate>
      <link>https://forem.com/techieshark/the-us-tech-force-just-launched-its-title-font-is-canadian-and-it-gets-worse-3d9k</link>
      <guid>https://forem.com/techieshark/the-us-tech-force-just-launched-its-title-font-is-canadian-and-it-gets-worse-3d9k</guid>
      <description>&lt;p&gt;If you've been paying attention to tech news, you may have seen that US Federal Government is launching "Tech Force":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Trump administration on Monday unveiled a new initiative dubbed the “U.S. Tech Force,” comprising about 1,000 engineers and other specialists who will work on artificial intelligence infrastructure and other technology projects throughout the federal government. - &lt;a href="https://www.cnbc.com/2025/12/15/trump-ai-tech-force-amazon-apple.html" rel="noopener noreferrer"&gt;CNBC&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if you visit their &lt;a href="https://techforce.gov/" rel="noopener noreferrer"&gt;website&lt;/a&gt; you might notice a couple things.&lt;/p&gt;

&lt;p&gt;Firstly, on the funny side, the title font for a group working on tech "for the American People" is firmly Canadian. &lt;/p&gt;

&lt;p&gt;The title font, "&lt;a href="https://pangrampangram.com/products/neue-montreal" rel="noopener noreferrer"&gt;PP Neue Montreal&lt;/a&gt;" is "inspired by what makes Montreal the great design city it is today". Montreal is of course, a city in Québec, which "&lt;a href="https://theconversation.com/is-trumps-assault-on-canada-bringing-quebec-and-the-rest-of-the-country-closer-together-250213" rel="noopener noreferrer"&gt;is the most anti-Donald Trump province in Canada&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qid5405zocq30h85dmp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qid5405zocq30h85dmp.png" alt="Browser showing title font for US Tech Force is the font " width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Secondly, on the not-so-funny side, "Tech Force" describes itself as "Tech for the American People", but it's hard not to notice the complete lack of actual people or humanity. &lt;/p&gt;

&lt;p&gt;The site's &lt;a href="https://techforce.gov/hero-two.webp" rel="noopener noreferrer"&gt;cover image&lt;/a&gt; looks as if taken by a spy drone on night reconnaissance over D.C. -- notably in one of the first cities in which the national guard was deployed ostensibly for crime fighting but which is now said to &lt;a href="https://www.msn.com/en-us/news/us/4-months-in-activists-say-trumps-operation-in-washington-targets-immigrants/ar-AA1SFqPB" rel="noopener noreferrer"&gt;target immigrants&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvgkogjjz7edzo3goog3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvgkogjjz7edzo3goog3.png" alt="Tech Force hero image - showing Washington DC from above at night" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tech Force's vision of what "for the American People" means seems ominous, but the vision of who they want to "answer the call" is completely Borg-level creepy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9osg5ue1rk30m040bhxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9osg5ue1rk30m040bhxh.png" alt="Screenshot of US Tech Force website from 21 Dec 2025" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tech Force is looking for "an elite group of ~1,000 technology specialists... to accelerate artificial intelligence (AI) implementation" but the ideal candidate appears to be a soulless black hooded automaton with no obvious connection to the world other than via a radio antennae attached to its cyborg CRT head.&lt;/p&gt;

&lt;p&gt;They appear to be quite literally looking for &lt;strong&gt;machine men&lt;/strong&gt; to build the AI machinery for the federal government.&lt;/p&gt;

&lt;p&gt;If thinking of applying, remember &lt;a href="https://www.charliechaplin.com/en/articles/29-The-Final-Speech-from-The-Great-Dictator-" rel="noopener noreferrer"&gt;Charlie Chaplin's words&lt;/a&gt;: "You are not machines!".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Machinery that gives abundance has left us in want. Our knowledge has made us cynical. Our cleverness, hard and unkind. We think too much and feel too little. More than machinery we need humanity. More than cleverness we need kindness and gentleness. Without these qualities, life will be violent and all will be lost…&lt;/p&gt;

&lt;p&gt;The misery that is now upon us is but the passing of greed - the bitterness of men who fear the way of human progress. The hate of men will pass, and dictators die, and the power they took from the people will return to the people. And so long as men die, liberty will never perish…&lt;/p&gt;

&lt;p&gt;Soldiers! don’t give yourselves to brutes - men who despise you - enslave you - who regiment your lives - tell you what to do - what to think and what to feel! Who drill you - diet you - treat you like cattle, use you as cannon fodder. Don’t give yourselves to these unnatural men - machine men with machine minds and machine hearts! You are not machines! You are not cattle! You are men! You have the love of humanity in your hearts! You don’t hate! Only the unloved hate - the unloved and the unnatural! Soldiers! Don’t fight for slavery! Fight for liberty!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;A literally 'non-American' typeface, a spy aesthetic, and a machine man - design is often intentional so you have to wonder: what was the real message? Perhaps that the entire project is a bit... un-American? What do you think? And, why?&lt;/p&gt;

</description>
      <category>news</category>
      <category>civictech</category>
      <category>design</category>
      <category>ai</category>
    </item>
    <item>
      <title>LTSP on LXD: A Fun Dev Trip</title>
      <dc:creator>Peter W</dc:creator>
      <pubDate>Thu, 11 Dec 2025 22:27:58 +0000</pubDate>
      <link>https://forem.com/techieshark/ltsp-on-lxd-a-fun-dev-trip-5nj</link>
      <guid>https://forem.com/techieshark/ltsp-on-lxd-a-fun-dev-trip-5nj</guid>
      <description>&lt;p&gt;Recently I've been excited about setting up a little 'homelab' to experiment with virtual machines (VMs), containers, Linux admin, and networking.&lt;/p&gt;

&lt;p&gt;There's just two problems: first, I don't yet have hard drives for my machines and second, I don't yet have a great place for the machines to be set up.&lt;/p&gt;

&lt;p&gt;To solve the first problem (lack of drives), I've been looking at a project I first played with years ago - LTSP, the &lt;a href="https://ltsp.org/" rel="noopener noreferrer"&gt;Linux Terminal Server Project&lt;/a&gt;. LTSP makes "maintaining tens or hundreds of &lt;strong&gt;diskless&lt;/strong&gt; clients is as easy as maintaining a single PC". Isn't that great?! The 'terminals' (client machines) will boot over the network without needing any permanent storage attached.&lt;/p&gt;

&lt;p&gt;To solve the second problem (lack of space), I'm initially going to entirely skip setting up any physical client machines at all. The idea is to run both the LTSP server and the LTSP client "machine" as containerized instances inside my dev box (e.g. laptop). There are a number of ways this can be done, and a lot of homelabbers might use Proxmox if they have that running already, but today I'm going to use software from Canonical (the company behind Ubuntu Linux) &amp;amp; others called "&lt;a href="https://canonical.com/lxd" rel="noopener noreferrer"&gt;LXD&lt;/a&gt;". Later, after getting the systems set up 'virtually', I could move plug in actual physical machines with very little changes and have it all just work. (That's the idea anyway!)&lt;/p&gt;

&lt;p&gt;In this post, I'll be talking about using LXD with LTSP, but if you had another set of instances you wanted to run for development purposes instead of LTSP, it should be simple to see how you'd do that. A note on terminology: I'll use "instances" to refer to either a virtual machine instance or a system container instance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Article © 2025. All rights reserved. Not for AI/ML training or data-mining use.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why LXD?
&lt;/h2&gt;

&lt;p&gt;We use LXD for its ability to manage virtual machines and system containers. &lt;/p&gt;

&lt;p&gt;If we wanted to run just a single application in a container, we'd use something like Docker.&lt;/p&gt;

&lt;p&gt;But in this case, we need to run the entire Linux operating system in a container, which is possible via the Linux kernel's "LXC" ("Linux Containers") system. We'll get an LTSP client set up as a VM (booting via iPXE) and the LTSP server set up as a system container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LXD provides both KVM-based VMs and system containers based on LXC – that can run a full Linux OS – in a single open source virtualisation platform. &lt;a href="https://canonical.com/lxd" rel="noopener noreferrer"&gt;ref&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note: LXC stands for "&lt;b&gt;L&lt;/b&gt;inu&lt;b&gt;x&lt;/b&gt; &lt;b&gt;C&lt;/b&gt;ontainers", so naturally LX&lt;b&gt;D&lt;/b&gt; stands for "Linux Container Daemon"; if "lxd" vs "lxc" seems confusing, &lt;a href="https://documentation.ubuntu.com/lxd/stable-5.21/explanation/lxd_lxc/" rel="noopener noreferrer"&gt;read this&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This set up also allows us to do development on a personal/development machine, without the need to do something like install proxmox on the bare metal. That saves us from needing a dedicated 'server' machine.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sidenote&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In 2023, Canonical took over more direct control of the LXD project (which they had sponsored from its beginning); this prompted the creation of a community fork, &lt;a href="https://linuxcontainers.org/incus/" rel="noopener noreferrer"&gt;Incus&lt;/a&gt;. I assume you can use either one fairly interchangeably, and &lt;a href="https://www.jmn.au/posts/switch-lxd-incus/" rel="noopener noreferrer"&gt;these notes&lt;/a&gt; may help if you wanted to do that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why LTSP
&lt;/h2&gt;

&lt;p&gt;As previously mentioned, Linux Terminal Server Project makes it easy to manage multiple diskless computers. That's why I'm using it here, but it could also be useful to anyone looking to reduce costs or admin efforts in e.g. a lab, a school, a home context, or heck even a &lt;a href="https://github.com/ltsp/ltsp/discussions/160" rel="noopener noreferrer"&gt;small research cluster&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;LTSP can be set up in a variety of ways. The hypothetical LTSP network I'll aim to emulate for this project is similar to what is shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5rslkd5owq6bi8bgf2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5rslkd5owq6bi8bgf2j.png" alt="Example illustration of LTSP network (illustrated via FossFlow). Shown in the network is an internet router, connected to a server, which connects to a switch, which then connects to multiple PCs" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(We'll simplify this a bit: since all our hypothetical clients are identical, to get this working we'll just start with a single client.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;If you'd like to follow along, here's what you'd need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A computer running Linux, with internet &amp;amp; root/sudo access. It may be easiest if you use some kind of Debian or Ubuntu variant. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enough space (on the above computer) to store the VM images.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! In a future post, I'll share more on testing this out with real client machines.&lt;/p&gt;

&lt;p&gt;Sidenote: you could even follow along without owning a Linux computer; by running a Linux VM from a Mac or Windows box, or even just online by using something like &lt;a href="https://killercoda.com/playgrounds/scenario/ubuntu" rel="noopener noreferrer"&gt;https://killercoda.com/playgrounds/scenario/ubuntu&lt;/a&gt; (I haven't tried it, so if you do, let us know if it works!).&lt;/p&gt;

&lt;p&gt;For this guide, we'll be using &lt;a href="https://www.linuxmint.com/" rel="noopener noreferrer"&gt;Linux Mint&lt;/a&gt; because that would be good in a school context. (Also because initially I tried the latest Ubuntu, 25.10, but ran into conflicts getting cloud-init and LTSP to play nice together, so we'll save you the headaches! I may write up how to work around those in a future post.)  &lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual Network Overview
&lt;/h2&gt;

&lt;p&gt;The broad system architecture of this is pretty simple - it's just two "computers", one for the server and one client (and later we could add any number of clients easily). As always though, the devil is in the details. &lt;/p&gt;

&lt;p&gt;The set up below aims to do two things: 1) it roughly matches the physical network topology illustrated above (where the server has two connections, one to the internet and one to a switch), and 2) it helps us get around &lt;a href="https://github.com/ltsp/ltsp/discussions/957" rel="noopener noreferrer"&gt;an issue&lt;/a&gt; you might otherwise run into in getting an LTSP 'network' working with LXD. Note also that the server can be either a VM or a "system container", but the client &lt;strong&gt;must&lt;/strong&gt; be a VM (so it can run the full boot process).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdmziq8f0vrmfii5r1el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdmziq8f0vrmfii5r1el.png" alt="LXD network diagram" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the unfamiliar, &lt;code&gt;lxdbr0&lt;/code&gt; and &lt;code&gt;lxdbr1&lt;/code&gt; are "bridge" devices created in the host, by LXD, which can be thought of as physical switches, and they allow the containers to communicate with anything else attached to the bridge. Thus, the server and client in our setup will communicate over &lt;code&gt;lxdbr1&lt;/code&gt;. These LXD-managed devices also come with extra goodies, like the ability to provide containers with IP addresses via DHCP, or have internet access through the host using &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation" rel="noopener noreferrer"&gt;NAT&lt;/a&gt;. Using NAT and DHCP, our &lt;code&gt;lxdbr0&lt;/code&gt; is effectively acting like the internet router in the physical network topology example shown earlier.&lt;/p&gt;

&lt;p&gt;Now that you know where we're aiming, let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install LXD
&lt;/h2&gt;

&lt;p&gt;Below you'll see the bare minimum commands which should work on a modern Ubuntu-based system. You may need to refer to the &lt;a href="https://documentation.ubuntu.com/lxd" rel="noopener noreferrer"&gt;LXD docs&lt;/a&gt; and in particular the &lt;a href="https://documentation.ubuntu.com/lxd/stable-5.21/tutorial/first_steps/#first-steps" rel="noopener noreferrer"&gt;'first steps'&lt;/a&gt; if your situation differs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo snap install lxd
sudo lxd init # this must be done as root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll then be prompted to configure LXD. Perhaps the only change I'd recommend is just use "dir" for the storage backend (this seems the simplest setup for development/testing purposes, as it uses a local directory for storage).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo lxd init
Would you like to use LXD clustering? (yes/no) [default=no]: 
Do you want to configure a new storage pool? (yes/no) [default=yes]: 
Name of the new storage pool [default=default]: 
Name of the storage backend to use (ceph, lvm, pure, btrfs, dir, powerflex, zfs) [default=zfs]: dir
Would you like to connect to a MAAS server? (yes/no) [default=no]: no
Would you like to create a new local network bridge? (yes/no) [default=yes]: 
What should the new bridge be called? [default=lxdbr0]: 
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: 
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: 
Would you like the LXD server to be available over the network? (yes/no) [default=no]:    
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]: 
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you later want to see the config, run &lt;code&gt;lxd init --dump&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make life easy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;‘lxc’ commands can be run as any user who is a member of group &lt;code&gt;lxd&lt;/code&gt; &lt;a href="https://documentation.ubuntu.com/server/how-to/containers/lxd-containers/index.html" rel="noopener noreferrer"&gt;ref&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo adduser `whoami` lxd 
# note: above, `whoami` will insert your username

newgrp lxd
# note, 'newgrp' only needed to avoid having to logout/login
# for the group addition to take effect

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: re: adding a user to &lt;code&gt;lxd&lt;/code&gt; group: "you should only give such access to users who you'd trust with root access to your system." &lt;a href="https://github.com/canonical/lxd#security" rel="noopener noreferrer"&gt;LXD Security&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally for the host setup, we will want a way to view the VGA video output from our client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install spice-client-gtk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Access the LXD web UI
&lt;/h2&gt;

&lt;p&gt;Let's get LXD's admin interface working, so you can use it to easily inspect or change anything we create.&lt;/p&gt;

&lt;p&gt;Follow &lt;a href="https://documentation.ubuntu.com/lxd/latest/howto/access_ui/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; to setup the LXD admin web UI access.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure that your LXD server is exposed to the network. You can expose the server during initialization, or afterwards by setting the core.https_address server configuration option.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set core.https_address :8443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we should be able to open web browser to &lt;a href="https://localhost:8443" rel="noopener noreferrer"&gt;https://localhost:8443&lt;/a&gt; - you'll likely see a security warning due to a self-signed certificate, so you'll need to click through to accept the risk and continue.&lt;/p&gt;

&lt;p&gt;Next up, you'll need to follow the instructions on the LXD admin website for how to set up a client certificate for TLS login:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c56fla7xt478wziex2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c56fla7xt478wziex2n.png" alt="LXD admin web console prompting the user to download a certificate and install it in the web browser" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once logged in with the TLS cert, you'll be shown some instructions for generating a trust token. It should be something like below (and you'll be asked to paste the generated token into the web admin page).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc auth identity create tls/lxd-ui --group admins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It'll be easiest to do the above steps from your Linux box. If you try accessing that website from another machine on your network, you might get blocked by your Linux machine's built-in firewall. But assuming you don't take that advice, and wanted to, say, connect to the linux box from your desktop running on 10.0.0.2, if your Linux machine is Ubuntu you could use a &lt;a href="https://help.ubuntu.com/community/UFW" rel="noopener noreferrer"&gt;ufw&lt;/a&gt; rule like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw allow from 10.0.0.2 to any port 8443 proto tcp comment 'Access LXD from desktop on IP=10.0.0.2'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By this point, you should now be able to access the web UI (which would look similar after our VMs are created in a later step):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2x4tynbvf22g3qfpjonv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2x4tynbvf22g3qfpjonv.png" alt="LXD web admin UI showing status of running containers/vms" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The GUI can be very helpful, but if anything goes wrong, don't worry - we can actually do everything via the command line.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 [optional]: create a test instance
&lt;/h2&gt;

&lt;p&gt;Let's create and run a virtual machine just to confirm that the basic LXD system is all working. The purpose of this step is also to familiarize you with some of the &lt;code&gt;lxc&lt;/code&gt; commands (so you can skip it if already familiar.)&lt;/p&gt;

&lt;p&gt;Run the command below, and be ready to go for a walk as the image download may take a while!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc launch ubuntu:25.10 sample-vm --vm
# alternatively, "... launch ubuntu:questing ..."
# since Ubuntu 25.10 == Ubuntu Questing, outcome is the same
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the "--vm" (virtual machine) flag. Virtual machines will use more resources than a system container. VMs also include the kernel files that we may need to share with clients over the network -- if you use a container, you'll also need to install a kernel. &lt;/p&gt;

&lt;p&gt;Verify the VM has launched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc list -c nstm # for columns below; "lxc list" for default columns
+-------------+---------+-----------------+--------------+
|    NAME     |  STATE  |      TYPE       | MEMORY USAGE |
+-------------+---------+-----------------+--------------+
| sample-vm   | RUNNING | VIRTUAL-MACHINE | 499.82MiB    |
+-------------+---------+-----------------+--------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If desired, the emulated hardware can be configured like so (which could be useful, for example, to test out a limited-resource client VM):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set sample-vm limits.cpu=1
lxc config set sample-vm limits.memory=500MiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can log into the VM (&lt;code&gt;sample-vm&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec sample-vm bash

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once logged in, verify the VM is able to connect to the internet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@sample-vm:~# apt update
Hit:1 http://archive.ubuntu.com/ubuntu questing InRelease
Get:2 http://security.ubuntu.com/ubuntu questing-security InRelease [136 kB]
Hit:3 http://archive.ubuntu.com/ubuntu questing-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu questing-backports InRelease
Get:5 http://security.ubuntu.com/ubuntu questing-security/main amd64 Components [208 B]
Get:6 http://security.ubuntu.com/ubuntu questing-security/universe amd64 Components [208 B]
Get:7 http://security.ubuntu.com/ubuntu questing-security/restricted amd64 Components [212 B]
Get:8 http://security.ubuntu.com/ubuntu questing-security/multiverse amd64 Components [212 B]
Fetched 136 kB in 2s (54.6 kB/s)
All packages are up to date.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Create the LTSP network
&lt;/h2&gt;

&lt;p&gt;Before creating the server and client, we need to create the &lt;code&gt;lxdbr1&lt;/code&gt; bridge/network that we will use to connect those instances. The settings we use here will use the LTSP standard/example subnet (192.168.67.1/24), and we don't need NAT, and it's important that we disable LXD's DHCP, as LTSP will be providing a PXE-enabled DHCP for the clients to enable network booting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc network create lxdbr1 \
  ipv4.address=192.168.67.1/24 \
  ipv4.nat=false \
  ipv4.dhcp=false \
  ipv6.address=none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might wonder why we don't need to create &lt;code&gt;lxdbr0&lt;/code&gt;. That's because that was already created for us by LXD after we ran &lt;code&gt;sudo lxd init&lt;/code&gt;; you'll recall it asked these questions and the default answers have set you up with a local network bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;lxc network list&lt;/code&gt; should confirm that we now have our two bridge networks ready, so we're ready to create our instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Create &amp;amp; configure the server container
&lt;/h2&gt;

&lt;p&gt;Create the server container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc init images:mint/xia ltsp-server 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get LTSP to work in a server container, we'll need to relax some of LXD's security. Only do this if you trust everything running in the container, and are comfortable with something that is described as &lt;a href="https://linuxcontainers.org/lxc/security/" rel="noopener noreferrer"&gt;"not safe at all"&lt;/a&gt; and is &lt;a href="https://discuss.linuxcontainers.org/t/what-does-security-nesting-true/7156/8" rel="noopener noreferrer"&gt;strongly discouraged&lt;/a&gt; -- but I'm OK with this for now, since this is mainly for a personal development purpose, and I've &lt;a href="https://github.com/ltsp/ltsp/pull/960" rel="noopener noreferrer"&gt;explored other options&lt;/a&gt;. Please act responsibly if you are using any of this in production!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set ltsp-server security.nesting true
lxc config set ltsp-server security.privileged true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to run this little 3-step process to set up the container so it can use host loop devices (note: this also naturally reduces the security benefits of containerization, so again - act responsibly. If anyone has better ideas please share!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Set raw.lxc for loop device permissions
lxc config set ltsp-server raw.lxc "lxc.cgroup2.devices.allow = b 7:* rwm
lxc.cgroup2.devices.allow = c 10:237 rwm"

# 1) Add loop-control device
lxc config device add ltsp-server loop-control unix-char path=/dev/loop-control source=/dev/loop-control

# 2) Count loop devices on host
HOST_LOOP_COUNT=$(ls /dev/loop[0-9]* 2&amp;gt;/dev/null | wc -l)
echo "Found $HOST_LOOP_COUNT loop devices on host"

# 3) Add matching number of loop devices to container
for i in $(seq 0 $((HOST_LOOP_COUNT - 1))); do
  if [ -e /dev/loop$i ]; then
    lxc config device add ltsp-server loop$i unix-block path=/dev/loop$i source=/dev/loop$i
  fi
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally, bump up the resources allocated to the server. I do this just so that later, when we build a compressed image of the server's file system, it'll run quickly, but adjust these as you see fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set ltsp-server limits.cpu=5
lxc config set ltsp-server limits.memory=3GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can also be set on the web UI via &lt;em&gt;Instances&lt;/em&gt; ⤑ &lt;em&gt;[Instance name]&lt;/em&gt; ⤑ &lt;em&gt;Configuration&lt;/em&gt; ⤑ &lt;em&gt;Resource limits&lt;/em&gt;, shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mdsr5rapm5ln70bh8pm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mdsr5rapm5ln70bh8pm.png" alt="Setting LXD Instance Resource Limits" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server will ultimately have two (virtual) network interface cards,  one attached to each of the bridges. The first NIC will take its IP address from &lt;code&gt;lxdbr0&lt;/code&gt; (which uses LXD's DHCP provider), but the second NIC be attached to the &lt;code&gt;lxdbr1&lt;/code&gt; which has DHCP disabled, so we'll need to assign a static IP address to this second NIC. (If LXD's DHCP were enabled on the bridge, we'd be able to have LXD provide a pre-set IP, but with DHCP disabled we'll have to use the distribution's standard method for assigning an IP from within the server container.)&lt;/p&gt;

&lt;p&gt;Connect &lt;strong&gt;&lt;code&gt;eth1&lt;/code&gt;&lt;/strong&gt; to the &lt;code&gt;lxdbr1&lt;/code&gt; bridge (LTSP internal network). Note that &lt;code&gt;eth0&lt;/code&gt; would have already been connected to &lt;code&gt;lxdbr0&lt;/code&gt; by default when our container was created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config device add ltsp-server eth1 nic \
  network=lxdbr1 \
  name=eth1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc start ltsp-server
# startup is very quick (compared to a VM)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you can confirm that the server has an IP address (which came from DHCP) on eth0, but has no IPv4 address on eth1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec ltsp-server -- ip --brief address
lo     UNKNOWN  127.0.0.1/8
eth0   UP       10.81.43.103/24 ... &amp;lt;----- comes from LXD's DHCP
eth1   UP        ... &amp;lt;--- no IPv4 address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a netplan file so &lt;code&gt;eth1&lt;/code&gt;'s static IP address will persist across reboots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec ltsp-server -- bash -c 'cat &amp;gt; /etc/netplan/60-ltsp-static.yaml &amp;lt;&amp;lt; EOF
network:
  version: 2
  ethernets:
    eth1:
      addresses:
        - 192.168.67.2/24
EOF'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix permissions so netplan won't complain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec ltsp-server -- sh -c 'chmod 600 /etc/netplan/*.yaml'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, apply this change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec ltsp-server -- netplan apply
# may give "WARNING:root:Cannot call Open vSwitch: ovsdb-server.service is not running." but can be ignored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you'll be able verify the change took effect; let's look at just the IPv4 addresses (&lt;code&gt;-4&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec ltsp-server -- ip --br -4 a
lo      UNKNOWN  127.0.0.1/8
eth0    UP       10.81.43.103/24
eth1    UP       192.168.67.2/24 # &amp;lt;-- now set
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Create the client VM
&lt;/h2&gt;

&lt;p&gt;Create the client VM as an emtpy instance (we don't need a disk, as we will be doing netbooting)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc init ltsp-client --vm --empty
# emulate a low-resource client (optional):
lxc config set ltsp-client limits.cpu=1
lxc config set ltsp-client limits.memory=500MiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to disable &lt;a href="https://wiki.ubuntu.com/UEFI/SecureBoot" rel="noopener noreferrer"&gt;UEFI Secure Boot&lt;/a&gt; so the VM will boot up. I'm asserting that should be fine, since 1) this writeup is meant for a developer context, and 2) we control the VMs and the virtual network between them. But if for some reason you're not comfortable with this, &lt;a href="https://github.com/ipxe/ipxe/discussions/358" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;a href="https://wiki.ubuntu.com/UEFI/SecureBoot/PXE-IPv6" rel="noopener noreferrer"&gt;are&lt;/a&gt; &lt;a href="https://hannan.au/posts/pxe/" rel="noopener noreferrer"&gt;some&lt;/a&gt; &lt;a href="https://wiki.debian.org/SecureBoot/VirtualMachine" rel="noopener noreferrer"&gt;resources&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set ltsp-client security.secureboot=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the &lt;code&gt;eth0&lt;/code&gt; device is attached to our LTSP network bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set ltsp-client agent.nic_config=true
lxc config device add ltsp-client eth0 nic \
  network=lxdbr1 \
  name=eth0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the client should be created but not yet started. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc list -c nst
+-------------+---------+-----------------+
|    NAME     |  STATE  |      TYPE       |
+-------------+---------+-----------------+
| ltsp-client | STOPPED | VIRTUAL-MACHINE |
+-------------+---------+-----------------+
| ltsp-server | RUNNING | CONTAINER       |
+-------------+---------+-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Super. However, we won't/can't start the client just yet, because we first need to set up the LTSP software and config back on our server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Install LTSP on the server container
&lt;/h2&gt;

&lt;p&gt;We won't deviate much from the standard &lt;a href="https://ltsp.org/docs/" rel="noopener noreferrer"&gt;LTSP docs&lt;/a&gt;, except for a couple things.&lt;/p&gt;

&lt;p&gt;The first wrinkle is that (as noted &lt;a href="https://github.com/ltsp/ltsp/discussions/956#discussioncomment-15122791" rel="noopener noreferrer"&gt;here&lt;/a&gt;) it is recommended to use an Ubuntu LTS release for production and use the PPA, but since we're using Linux Mint we'll skip adding the PPA.&lt;/p&gt;

&lt;p&gt;While we could do &lt;code&gt;lxc exec ltsp-server -- &amp;lt;command&amp;gt;&lt;/code&gt;, we can also just run a bash shell on the server, so we'll do that (note the prompt will change to reflect that we're logged in):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc exec ltsp-server bash

&amp;lt;for commands below, we're in our server's bash shell&amp;gt;

root@ltsp-server# # skipping this: add-apt-repository ppa:ltsp/ppa
root@ltsp-server# sudo apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://ltsp.org/docs/installation/#adding-the-ltsp-ppa" rel="noopener noreferrer"&gt;Ref&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second deviation from the docs is that we'll need to install the kernel files. Typically containers don’t have these files as they don’t need them, but since we are using the server’s file system as the source of our client image, we will need these so the client can boot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@ltsp-server# apt install --no-install-recommends linux-generic initramfs-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the LTSP server packages (&lt;a href="https://ltsp.org/docs/installation/#installing-ltsp-server-packages" rel="noopener noreferrer"&gt;docs ref&lt;/a&gt;). Note that (per docs) we install &lt;code&gt;ipxe&lt;/code&gt; rather than &lt;code&gt;ltsp-binaries&lt;/code&gt; package since we're not using the PPA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@ltsp-server# apt install ipxe 
root@ltsp-server# apt install --install-recommends ltsp dnsmasq nfs-kernel-server openssh-server squashfs-tools ethtool net-tools epoptes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;apt install&lt;/code&gt; you may observe a failure to start &lt;code&gt;dnsmasq&lt;/code&gt; (see below). That is safe to ignore, for reasons &lt;a href="https://github.com/ltsp/ltsp/discussions/449#discussioncomment-643889" rel="noopener noreferrer"&gt;explained here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Setting up dnsmasq (2.90-0ubuntu0.22.04.1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/dnsmasq.service → /lib/systemd/system/dnsmasq.service.
Job for dnsmasq.service failed because the control process exited with error code.
See "systemctl status dnsmasq.service" and "journalctl -xeu dnsmasq.service" for details.
...
Starting dnsmasq - A lightweight DHCP and caching DNS server...
dnsmasq: failed to create listening socket for port 53: Address already in use
dnsmasq.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
dnsmasq[6780]: failed to create listening socket for port 53: Address already in use
...
systemd[1]: dnsmasq.service: Failed with result 'exit-code'.
systemd[1]: Failed to start dnsmasq - A lightweight DHCP and caching DNS server.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're ready to let LTSP configure &lt;code&gt;dnsmasq&lt;/code&gt; (which sets up DHCP with PXE network booting for the clients). Read &lt;a href="https://ltsp.org/docs/installation/#network-configuration" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; but for this setup, we'll just need to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@ltsp-server# ltsp dnsmasq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's create a user. Below we'll call it &lt;code&gt;sam&lt;/code&gt; (for "System AdMin") but you can call it anything. The user will be able to log into our LTSP server and the client, and by adding to the 'epoptes' group the user will have special admin access for the LTSP network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@ltsp-server# useradd sam
root@ltsp-server# passwd sam # pick a secure password
root@ltsp-server# gpasswd -a sam epoptes # only do this step for admin users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8: Create the LTSP image &amp;amp; complete LTSP setup
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;LTSP supports three methods to maintain a client image. They are documented in the ltsp image man page. &lt;a href="https://ltsp.org/docs/installation/#maintaining-a-client-image" rel="noopener noreferrer"&gt;Ref&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We will be using the first method ("chrootless") here.&lt;/p&gt;

&lt;p&gt;Build an image based on the LTSP server's filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ltsp image /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sidenote: if for some reason your linux distro has a separate &lt;code&gt;/boot&lt;/code&gt; partition, you would need to replace the above command with: &lt;code&gt;ltsp image /,,/boot,subdir=boot&lt;/code&gt;. This comes from &lt;a href="https://github.com/ltsp/ltsp/issues/43#issuecomment-541269010" rel="noopener noreferrer"&gt;a recommendation in the LTSP discussion forums&lt;/a&gt; and for more info, see the &lt;a href="https://ltsp.org/man/ltsp-image/#examples" rel="noopener noreferrer"&gt;&lt;code&gt;ltsp image&lt;/code&gt; manpage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This step may take a few minutes, but you should see it progressing and some output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ltsp image /
Using x86_64 as the base name of image /
Running: mount -t tmpfs -o mode=0755 tmpfs /tmp/tmp.wWZ787eg3d/tmpfs
Running: mount -t overlay -o upperdir=/tmp/tmp.wWZ787eg3d/tmpfs/0/up,lowerdir=/,workdir=/tmp/tmp.wWZ787eg3d/tmpfs/0/work /tmp/tmp.wWZ787eg3d/tmpfs /tmp/tmp.wWZ787eg3d/root/
Trying to acquire package management lock: /var/lib/dpkg/lock
Cleaning up x86_64 before mksquashfs...
Replacing /tmp/tmp.wWZ787eg3d/root/etc/ssh/ssh_host_ecdsa_key
Replacing /tmp/tmp.wWZ787eg3d/root/etc/ssh/ssh_host_ed25519_key
Replacing /tmp/tmp.wWZ787eg3d/root/etc/ssh/ssh_host_rsa_key
Parallel mksquashfs: Using 5 processors
Creating 4.0 filesystem on /srv/ltsp/images/x86_64.img.tmp, block size 131072.
[====================================================================================================================================| ] 35182/35228  99%
Unrecognised xattr prefix system.posix_acl_access
Unrecognised xattr prefix system.posix_acl_default
[=====================================================================================================================================|] 35228/35228 100%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also observed, below, the failed attempts to remove files under &lt;code&gt;/tmp&lt;/code&gt; -- this seems like a &lt;a href="https://github.com/ltsp/ltsp/discussions/673" rel="noopener noreferrer"&gt;known issue&lt;/a&gt; and seems safe to ignore for our purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rmdir: failed to remove '/tmp/tmp.SR8el2YEmh/tmpfs': Directory not empty
LTSP command failed: rmdir /tmp/tmp.SR8el2YEmh/root /tmp/tmp.SR8el2YEmh/tmpfs
rmdir: failed to remove '/tmp/tmp.SR8el2YEmh': Directory not empty
LTSP command failed: rmdir /tmp/tmp.SR8el2YEmh
Running: ltsp kernel /srv/ltsp/images/x86_64.img
Running: mount -t squashfs -o ro /srv/ltsp/images/x86_64.img /tmp/tmp.5MzaNEOmF0/tmpfs/0/looproot
Running: mount -t overlay -o upperdir=/tmp/tmp.5MzaNEOmF0/tmpfs/0/up,lowerdir=/tmp/tmp.5MzaNEOmF0/tmpfs/0/looproot,workdir=/tmp/tmp.5MzaNEOmF0/tmpfs/0/work /tmp/tmp.5MzaNEOmF0/tmpfs /tmp/tmp.5MzaNEOmF0/root/
-rw-r--r-- 1 root root 62933977 Dec  1 09:43 /srv/tftp/ltsp/x86_64/initrd.img
-rw-r--r-- 1 root root 16542088 Dec 1 08:41 /srv/tftp/ltsp/x86_64/vmlinuz
rmdir: failed to remove '/tmp/tmp.5MzaNEOmF0/tmpfs': Directory not empty
LTSP command failed: rmdir /tmp/tmp.5MzaNEOmF0/root /tmp/tmp.5MzaNEOmF0/tmpfs
rmdir: failed to remove '/tmp/tmp.5MzaNEOmF0': Directory not empty
LTSP command failed: rmdir /tmp/tmp.5MzaNEOmF0
To update the iPXE menu, run: ltsp ipxe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important note, from &lt;a href="https://ltsp.org/docs/installation/#maintaining-a-client-image" rel="noopener noreferrer"&gt;the LTSP docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need to run these commands every time you install new software or updates to your image and want to export its updated version&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now continuing the commands listed in the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@ltsp-server:~# ltsp ipxe
root@ltsp-server:~# ltsp nfs
root@ltsp-server:~# ltsp initrd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay particular attention to this bit about &lt;code&gt;ltsp initrd&lt;/code&gt; (again, straight from the docs):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This compresses /usr/share/ltsp, /etc/ltsp, /etc/{passwd,group} and the server public SSH keys into /srv/tftp/ltsp/ltsp.img, which is transferred as an "additional initrd" to the clients when they boot. You can read about its benefits in its man page, for now keep in mind that you need to run ltsp initrd after each LTSP package update, or when you add new users, or if you create or modify /etc/ltsp/ltsp.conf, which replaced the LTSP 5 "lts.conf".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that, we're now ready to try booting the LTSP client VM!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: "Boot up" the client VM over the network
&lt;/h2&gt;

&lt;p&gt;At this point, our LTSP server VM is running but the client is not, which we can quickly confirm below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc list -c ns
+-------------+---------+
|    NAME     |  STATE  |
+-------------+---------+
| ltsp-client | STOPPED |
+-------------+---------+
| ltsp-server | RUNNING |
+-------------+---------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're now ready to start the client and have it boot over the virtual network from the image that was built via the previous step. &lt;/p&gt;

&lt;p&gt;This process uses &lt;a href="https://en.wikipedia.org/wiki/Preboot_Execution_Environment" rel="noopener noreferrer"&gt;PXE&lt;/a&gt;, the "Preboot eXecution Environment". A client booting with PXE will use DHCP to communicate with the server early in the boot sequence: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[The client] broadcasts a DHCPDISCOVER packet containing PXE-specific options to port 67/UDP (DHCP server port); it asks for the required network configuration and network booting parameters. The PXE-specific options identify the initiated DHCP transaction as a PXE transaction. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So let's have the LTSP server show us the DHCP network traffic on ports 67 (which the server listens on) and 68 (which the server responds on).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# back on the host
lxc exec ltsp-server -- ip -br a # confirm eth1 is on the 192.x net
lxc exec ltsp-server -- apt install tcpdump
lxc exec ltsp-server -- tcpdump -vvv -i eth1 -n port 67 or port 68
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the server is just waiting for clients to boot up. Next, we'll boot up the client and you'll see two things: 1) on that server shell you'll see the DHCP packets (first a &lt;code&gt;DHCPDISCOVER&lt;/code&gt; message from the client, then an &lt;code&gt;OFFER&lt;/code&gt; from the server, and so on), and 2) you'll see the client boot up on an emulated monitor.&lt;/p&gt;

&lt;p&gt;Start the client &amp;amp; watch it boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc start ltsp-client &amp;amp;&amp;amp; \
lxc console ltsp-client --type=vga
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will open a GUI window showing the boot up process (similar to what you'd see from power-on of actual hardware). If it fails, make sure you installed the &lt;code&gt;spice-client-gtk&lt;/code&gt; in a previous step.&lt;/p&gt;

&lt;p&gt;First you'll see the BIOS acquire a DHCP address and download the initial file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fth4vsfilfqpeoq4n35ez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fth4vsfilfqpeoq4n35ez.png" alt="Start of PXE boot up" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you'll see the iPXE menu; either hit Enter or just let it boot from the default option (x86_64.img):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyopabqyqndfwhscy6hwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyopabqyqndfwhscy6hwg.png" alt="iPXE boot menu" width="732" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you'll see the LTSP image files being loaded:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3ndqms8v6wb9deooua5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3ndqms8v6wb9deooua5.png" alt="Loading kernel files" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this the boot process switches to outputting in video mode only, hence why you needed the "--type=vga" command line argument earlier. If you inadvertently left that argument off, the boot process continues in the background but it could just look hung here as you don't see any updates (in which case: hit Ctrl-A then Q, then jump back into the video console: &lt;code&gt;lxc console ltsp-client --type=vga&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If all goes well, you would see the rest of the boot process and finally a login screen, and you'll be able to log in to the LTSP client as the user we created (on the server!) earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheqxdei2uz0twcye0hp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheqxdei2uz0twcye0hp5.png" alt="LTSP client login" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When ready to shut down, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc stop --force ltsp-client # --force is optional but faster, probs don't use in production!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Troubleshooting&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the boot process is only partially successful and displays an error about 'Access Denied', you may need to shut down the VM, run the following command, then try starting the VM again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set ltsp-client security.secureboot=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If while running the &lt;code&gt;lxc console...&lt;/code&gt; command you get an error like &lt;code&gt;unshare: write failed /proc/self/uid_map: Operation not permitted&lt;/code&gt;, then use the &lt;a href="https://github.com/canonical/lxd/issues/12882#issuecomment-1941766215" rel="noopener noreferrer"&gt;workaround here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By this point, we have successfully emulated a complete Linux Terminal Server Project network (server + client) running as virtual machines on a single physical machine with LXD installed. &lt;/p&gt;

&lt;p&gt;I'm happy to now have a base for LTSP server+client devops explorations from the fully contained comforts of my laptop at home or on the road.&lt;/p&gt;

&lt;p&gt;Hopefully you've enjoyed this tour of LXD + LTSP and learned something useful along the way.&lt;/p&gt;

&lt;p&gt;If you found this useful, or ran into any questions, or have ideas for other ways to use LXD and/or LTSP, please leave a comment, I'd love to hear about it!&lt;/p&gt;

&lt;p&gt;In a future post I may cover: 1) adding a GUI environment to our client and 2) setting up a physical machine to netboot from our LXD LTSP server container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other resources &amp;amp; further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ltsp.org/" rel="noopener noreferrer"&gt;LTSP website &amp;amp; docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://documentation.ubuntu.com/lxd/latest/" rel="noopener noreferrer"&gt;LXD official docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://images.lxd.canonical.com" rel="noopener noreferrer"&gt;LXD images&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking" rel="noopener noreferrer"&gt;Introduction to Linux interfaces for virtual networking&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/LXD" rel="noopener noreferrer"&gt;Arch Wiki &amp;gt; LXD&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/rimelek/creating-virtual-machines-with-lxd-581k"&gt;Creating virtual machines with LXD&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ltsp/ltsp/discussions/581#discussioncomment-1649242" rel="noopener noreferrer"&gt;https://github.com/ltsp/ltsp/discussions/581#discussioncomment-1649242&lt;/a&gt; which mentions setup with loopback devices, container privilege etc&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ltsp/ltsp/pull/960" rel="noopener noreferrer"&gt;https://github.com/ltsp/ltsp/pull/960&lt;/a&gt; to Enable squashfuse to avoid need for /dev/loop devices &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things tried at one point or another but probably don't need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc config set ltsp-server linux.kernel_modules overlay,squashfs,nfsd,nfs_acl,lockd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Copyright notice
&lt;/h4&gt;

&lt;p&gt;© 2025 Peter W. All rights reserved. Not for AI/ML training or data-mining use.&lt;/p&gt;

&lt;p&gt;NO AI TRAINING: Without in any way limiting the author’s exclusive rights under copyright, any use of this publication to “train” generative artificial intelligence (AI) technologies to generate text is expressly prohibited. The author reserves all rights to license uses of this work for generative AI training and development of machine learning language models. &lt;/p&gt;

</description>
      <category>linux</category>
      <category>containers</category>
      <category>devops</category>
      <category>homelab</category>
    </item>
  </channel>
</rss>
