Docker Commands Taking 1–2 Minutes to Respond

Status: CURRENT — ENVIRONMENT-SPECIFIC FIX
Last reviewed: 31 August 2026
Applies to: Debian-based Proxmox LXC containers running Docker where systemd-networkd-wait-online is enabled even though networking is actually handled by ifupdown

My Docker commands were taking roughly two minutes to respond after the LXC started. Docker itself wasn’t slow. A redundant systemd-networkd-wait-online.service was sitting there for about two minutes waiting for a network state that was never going to arrive, while Docker waited behind network-online.target.

This one annoyed me because Docker looked guilty as hell.

I would run:

docker ps


and… nothing.

No error. No useful message. Just a blinking cursor for a good minute or two.

Then suddenly the command would return and basically every container showed:

Up Less than a second


Which was the first clue that Docker hadn’t been sitting there slowly thinking about the answer. Docker had only just been allowed to bloody start.

It also seemed to be affecting Plex

This LXC also runs my Plex server.

Before I fixed the network wait, refreshing app.plex.tv would load my Plex account quickly enough, but my own server/library could take another 10–15 seconds to actually appear.

After fixing this Docker/network startup mess, the library population became basically instant.

I can’t prove the extra Plex delay was caused by exactly the same systemd wait-online mechanism. What I can say is that both problems disappeared when I cleaned up the networking on this LXC. So I would treat slow Plex server discovery as another clue that the host/container networking is worth looking at rather than immediately blaming Plex itself.

It also appeared to clear up some of the weird Plex indirect/relay behaviour I’d been seeing around the same time. Again: observed before/after, not a peer-reviewed scientific paper about my Plex box 😀

First: prove Docker is actually the thing that’s slow

Check Docker:

systemctl status docker --no-pager -l
systemctl status containerd --no-pager -l


Then look at boot timing:

systemd-analyze blame | head -30
systemd-analyze critical-chain docker.service


My result was hilariously obvious:

2min 149ms systemd-networkd-wait-online.service
    1.052s docker.service
    1.011s ifupdown-wait-online.service


Docker itself took about one second.

The two-minute corpse lying across the road was systemd-networkd-wait-online.service.

The critical chain made it even clearer

systemd-analyze critical-chain docker.service


Mine showed:

docker.service +1.052s
└─network-online.target @2min 384ms
  └─ifupdown-wait-online.service @123ms +1.011s


So the Docker daemon wasn’t taking two minutes to initialise. It was ordered behind network-online.target, and that target wasn’t being considered ready until the networking wait mess had finished.

systemd’s own documentation describes network-online.target as an active synchronisation point that can deliberately delay services until networking is considered online. The matching wait-online service is supposed to come from the network manager you’re actually using.

Key phrase there:

the network manager you're actually using


I had two wait-online systems in the same LXC

This command exposed the stupid bit:

systemctl list-units --all | grep -E 'wait-online|network-online'


My LXC had:

ifupdown-wait-online.service
    active / exited

systemd-networkd-wait-online.service
    failed / failed

network-online.target
    active


And:

systemctl --failed


showed systemd-networkd-wait-online.service sitting there failed.

This Debian LXC was using the traditional ifupdown networking from /etc/network/interfaces. It already had ifupdown-wait-online.service, which completed in about one second.

Meanwhile systemd-networkd-wait-online was also enabled and waiting for systemd-networkd’s idea of an online network.

It never got happy, so it eventually hit its timeout.

The current systemd manual says the default systemd-networkd-wait-online timeout is 120 seconds.

Oh look. There’s our two bloody minutes.

Check which network manager is actually configuring the LXC

Do not blindly disable systemd-networkd on every Linux machine because this article fixed my LXC. If your machine genuinely uses systemd-networkd, then its wait-online service may be completely legitimate and you need to fix the networkd configuration instead.

Check the classic Debian config first:

cat /etc/network/interfaces
ls -la /etc/network/interfaces.d/


Then check the services:

systemctl is-active networking
systemctl is-enabled ifupdown-wait-online.service
systemctl is-enabled systemd-networkd.service
systemctl is-enabled systemd-networkd-wait-online.service


And if you think systemd-networkd is genuinely in use:

networkctl list
networkctl status
ls -la /etc/systemd/network/


In my case, ifupdown was doing the actual networking job. The extra networkd wait-online service was just standing in the doorway refusing to let Docker through.

The fix in my LXC

Because this container was using ifupdown rather than systemd-networkd for its network configuration, I disabled the redundant wait-online service:

systemctl disable --now systemd-networkd-wait-online.service


If it has previously been force-enabled by something and keeps coming back, you can inspect its enablement first:

systemctl is-enabled systemd-networkd-wait-online.service
systemctl cat systemd-networkd-wait-online.service


I would only mask it if I had confirmed the LXC does not need it and something kept trying to start it:

systemctl mask systemd-networkd-wait-online.service


Masking is stronger than disabling, so don’t throw it around just because it sounds more decisive.

Should I disable systemd-networkd itself?

Not automatically.

The broken bit I actually found was the wait-online service. If your LXC is definitely configured entirely through ifupdown and systemd-networkd has no job at all, then removing the redundant network manager may also make sense:

systemctl disable --now systemd-networkd.service


But only do that after checking /etc/systemd/network, networkctl and your actual interface configuration.

Turning off the network manager that really owns your IP address is an impressively efficient way to turn a remote server into a trip across the house.

Reboot and test it properly

I rebooted the LXC because that was the scenario where the delay actually mattered:

reboot


Then immediately checked:

time docker ps
systemd-analyze blame | head -20
systemctl --failed


Instead of staring at a blinking cursor for two minutes, docker ps was basically instant.

And unlike before, the containers weren’t all reporting that they’d come alive one second ago because Docker had been held hostage for the first two minutes of the boot.

Why docker ps looked like it was hanging

My boot logs showed docker.socket becoming available very early, while docker.service was still waiting further down the boot chain.

Docker can be socket-activated by systemd. So from the user’s point of view:

run docker ps
    ↓
Docker client connects to /run/docker.sock
    ↓
systemd wants docker.service
    ↓
docker.service is waiting behind network-online.target
    ↓
systemd-networkd-wait-online sits there
    ↓
120 second timeout
    ↓
dockerd starts in ~1 second
    ↓
all containers start
    ↓
docker ps finally returns


Which is why this felt like a Docker command problem even though the real delay was happening one layer underneath it.

The Docker logs were actually pretty clean

Once Docker was allowed to start, the daemon did exactly what it was supposed to do:

Loading containers: done.
Daemon has completed initialization
API listen on /run/docker.sock


That was another useful clue.

If dockerd starts in a second and its logs show normal container restoration, don’t spend the next hour rebuilding Docker networks because a completely different systemd service stole 120 seconds before Docker even got a turn.

Ignore unrelated LXC noise until it proves it’s relevant

The logs also had some very scary-looking container-specific rubbish such as:

mount: /sys/kernel/config: permission denied
modprobe: FATAL: Module overlay not found in directory /lib/modules/...


Those messages are worth understanding, but they weren’t the thing consuming two minutes in my timing output.

This is why systemd-analyze blame was so useful. It stopped me chasing every red-looking log line and pointed directly at the bastard actually eating the time.

If you really do use systemd-networkd

Don’t disable its wait service just to make the number disappear.

The current systemd-networkd-wait-online tool can be told to:

  • Wait for a particular interface with --interface=.
  • Ignore interfaces with --ignore=.
  • Succeed when any suitable interface is online with --any.
  • Use a different timeout with --timeout=.
  • Ignore a link for online decisions using RequiredForOnline=no in its .network config.

If networkd genuinely owns the network, fix why it thinks an interface isn’t online. Don’t just remove the smoke alarm because it’s noisy.

My Plex result was the nice bonus

The Docker fix was obvious: two-minute CLI hang became instant.

The nicer surprise was Plex.

BEFORE

refresh app.plex.tv
      ↓
account loads
      ↓
wait...
      ↓
10–15 seconds
      ↓
my own library finally appears


AFTER

refresh app.plex.tv
      ↓
boom — library is there


I wouldn’t use that as proof that systemd-networkd-wait-online directly causes slow Plex discovery on every system.

But if Docker startup is stuck behind broken network readiness and Plex is acting weird with server discovery, relay/indirect connections or slow library population, I’d absolutely investigate the underlying Linux network state before pulling Plex apart.

Quick diagnosis version

# What is actually slow?
systemd-analyze blame | head -30

# What is Docker waiting for?
systemd-analyze critical-chain docker.service

# Do I have multiple wait-online systems?
systemctl list-units --all | grep -E 'wait-online|network-online'

# What has actually failed?
systemctl --failed

# Which networking system am I using?
cat /etc/network/interfaces
networkctl list


If you see:

ifupdown-wait-online.service            active
systemd-networkd-wait-online.service      failed
systemd-networkd-wait-online              ~120 seconds


and you’ve confirmed ifupdown is the real network manager, you’ve probably found the same stupid little problem I did.

Final result

Docker was not slow.

Docker:
  ~1 second

Wrong wait-online service:
  ~2 minutes

Plex library:
  10–15 seconds → instant

Time spent blaming Docker:
  more than Docker deserved


This is one of those fixes I love because nothing needed more CPU, more RAM, a Docker reinstall or some heroic network rebuild.

One stale/redundant service was sitting in the boot path waiting two minutes for something the LXC wasn’t even using.

Removed the pointless wait and suddenly Docker stopped looking drunk, Plex woke up properly, and the whole box felt normal again.

Linux: where the problem is never the thing you’re currently swearing at 😀

References

Intel Arc A310 Hardware Transcoding in a Proxmox LXC

Status: CURRENT
Last reviewed: 31 August 2026
Applies to: Proxmox VE 8.2+/9, unprivileged LXC, Intel Arc A310 and Plex hardware transcoding

This is how I pass my Intel Arc A310 through to an unprivileged Plex LXC on Proxmox. Current Proxmox can pass the render device straight into the container, so the old cgroup/idmap/bind-mount witchcraft is no longer required.

I use an Intel Arc A310 in my Proxmox box purely because it is a tiny little transcoding monster for Plex.

Plex itself runs inside an unprivileged LXC, with Plex running in Docker inside that container. Sounds like three layers of bullshit, but the GPU path is actually pretty simple:

Intel Arc A310
      ↓
Proxmox host
      ↓
/dev/dri/renderD128
      ↓
unprivileged Plex LXC
      ↓
Docker
      ↓
Plex
      ↓
hardware transcode :)


The important device for video transcoding is normally the render node. Plex on Linux uses Intel VAAPI for hardware decode/encode, so we don’t need to PCI-passthrough the whole GPU like we would with a VM.

1. Make sure Proxmox can see the Arc

On the Proxmox host:

lspci -k | grep -EA3 'VGA|3D|Display'

ls -la /dev/dri
ls -la /dev/dri/by-path


On my machine the Arc currently appears as:

/dev/dri/card1
/dev/dri/renderD128


Your cardX number can be different, especially if the CPU also has integrated graphics.

The by-path directory is handy for confirming which DRM device belongs to which PCI GPU before blindly passing through the first thing called renderD128.

Intel’s current Linux guidance says modern Ubuntu distributions have Arc graphics support in the normal distro/kernel stack; you generally don’t need to bolt on one of Intel’s old custom driver repositories just to make the GPU exist. The Intel Media Driver/VAAPI userspace components may still be needed by applications that use VAAPI.

2. Pass the GPU device into the LXC

This is the bit that has become much nicer in modern Proxmox.

Shut the Plex LXC down, then go to:

Plex LXC
→ Resources
→ Add
→ Device Passthrough


Add the render device:

/dev/dri/renderD128


I also pass the matching card device through because it makes GPU diagnostics inside the container easier:

/dev/dri/card1


Proxmox’s current pct tooling exposes these as normal dev0, dev1 device entries. No manual lxc.cgroup2.devices.allow, no hand-written bind mount, no sacrificing a goat to UID mapping.

The CLI equivalent looks like:

pct set 103 -dev0 /dev/dri/renderD128
pct set 103 -dev1 /dev/dri/card1


Then:

pct config 103


You should see the device entries in the container configuration.

3. Start the LXC and check /dev/dri

pct start 103
pct enter 103
ls -la /dev/dri


On my Plex LXC I currently have:

card1
renderD128


If the devices aren’t there, stop. Plex isn’t going to magically discover a GPU that the LXC cannot see.

4. Fix access to the render device

Inside my container the useful groups are:

video:x:44
render:x:105


My Plex user is a member of both:

getent group video
getent group render


For my current setup I get:

video:x:44:plex
render:x:105:root,plex,richay


If Plex isn’t in the groups, add it:

sudo usermod -aG video plex
sudo usermod -aG render plex


Then restart the service/container so the new group membership actually applies.

Quick permission test: Proxmox device passthrough also lets you assign a mode/GID to the device. Using something like mode=0666 can be useful for proving that a failure is purely permissions, but I wouldn’t leave the GPU world-writable just because it made the red error disappear. Fix the group access afterwards.

5. Pass /dev/dri into the Plex Docker container

Now the LXC has the GPU, Docker needs it too.

In my Plex Compose stack:

services:
  plex:
    devices:
      - /dev/dri:/dev/dri


Then recreate Plex:

docker compose up -d
docker exec plex ls -la /dev/dri


If Docker can see renderD128, we’ve successfully pushed the GPU through both layers:

Proxmox host     ✓
LXC              ✓
Docker container ✓
Plex             ...your turn mate


6. Enable hardware transcoding in Plex

In Plex Web:

Settings
→ Server
→ Transcoder
→ Show Advanced

✓ Use hardware acceleration when available
✓ Use hardware-accelerated video encoding


Plex currently requires Plex Pass for normal hardware-accelerated streaming.

If more than one GPU is available, Plex also has a Hardware transcoding device selector. I explicitly select the Intel Arc rather than leaving it on Auto when I want to be absolutely sure which device Plex is using.

Plex’s current Linux transcoder uses Intel VAAPI for both hardware decode and hardware encode when supported.

7. Actually prove it is working

Seeing the GPU in /dev/dri is nice. It does not prove Plex is using it.

Start playing a video, then deliberately force a transcode by dropping the playback quality to something like 480p.

Open the Plex Dashboard and expand the playback details.

What I want to see is:

Video
Transcode (hw)


Plex’s own documentation specifically says the (hw) marker is the way to confirm hardware acceleration is being used.

If it just says Transcode with no (hw), congratulations: your expensive little GPU is currently decorative.

Watching the Arc work from the Proxmox host

I also like watching the GPU directly while forcing a transcode.

Install Intel’s GPU tools on the Proxmox host if you don’t already have them:

apt update
apt install intel-gpu-tools


Then:

intel_gpu_top


Force a Plex transcode and you should see the media/video engines wake up.

This is my favourite test because the Plex Dashboard can say whatever lovely thing it wants — watching the GPU actually do work removes the guesswork.

Optional VAAPI test inside the LXC

If Plex still refuses to use the GPU, test VAAPI independently of Plex.

apt update
apt install vainfo intel-media-va-driver-non-free

vainfo --display drm --device /dev/dri/renderD128


If vainfo can enumerate the Arc’s decode/encode capabilities, the Linux/VAAPI path is alive and the problem is further up the stack.

Don’t install random Intel driver repositories just because an old guide tells you to. Intel’s current Linux guidance says modern Ubuntu releases include Arc graphics support in the normal distribution stack. Add extra driver packages only when your actual distro/application needs them.

The old way can stay dead

Older Proxmox LXC GPU guides tended to involve things like:

lxc.cgroup2.devices.allow = c 226:* rwm
lxc.mount.entry = /dev/dri ...
subuid
subgid
custom idmaps
chmod
prayer


Some of that was necessary at the time.

For a current Proxmox LXC, native device passthrough is dramatically cleaner. Proxmox’s current pct configuration supports passing a host device directly into a container and assigning the device node’s UID, GID and access mode if needed.

Use the modern feature before manually rebuilding the old plumbing underneath it.

My final setup

Intel Arc A310
    ↓
Proxmox device passthrough
    ↓
/dev/dri/renderD128
    ↓
Plex unprivileged LXC
    ↓
Docker /dev/dri mapping
    ↓
Plex VAAPI
    ↓
Transcode (hw)
    ↓
CPU gets to stop screaming :)


That’s really all there is to it now.

The Arc A310 doesn’t need the whole VM PCI-passthrough treatment for this job. Let the Proxmox host own the GPU, hand the render device to the LXC, hand it through Docker, and let Plex chew on it.

Much cleaner than the old janky config I eventually deleted — and considerably easier to remember the next time I inevitably break it myself 😀

References

Using a JetKVM as a Homelab Test Bench

Status: CURRENT
Last reviewed: 31 August 2026
Applies to: JetKVM used as a temporary PC/server test-bench KVM

I somehow spent time trying to work out a clever way to stop moving keyboards, mice and monitor cables around every time I test another PC… while already owning a JetKVM. Once that penny finally dropped, my test bench became considerably less stupid.

I test a fair bit of random homelab hardware.

Old PCs, replacement motherboards, Raspberry Pis, Proxmox boxes, fresh Linux installs, machines I’m trying to PXE boot, machines that were working five minutes ago and have now decided they no longer believe in Ethernet.

The annoying part wasn’t normally the computer itself.

It was this shit:

Unplug keyboard
Unplug mouse
Find spare monitor input
Find cable
Move cable
Realise target PC only has DisplayPort
Find another adapter
Crawl under desk
Boot machine
Need BIOS
Repeat forever


I was literally thinking I needed some sort of monitor/USB switch or dedicated test-bench setup.

Then I remembered:

I OWN A FUCKING JETKVM.


Oh.

Problem solved 😂.

What JetKVM actually replaces on my bench

JetKVM is a KVM-over-IP device — Keyboard, Video and Mouse over the network. It gives me the target computer’s display in a browser and presents remote keyboard/mouse input back to that machine.

The key difference from RDP, SSH, VNC, AnyDesk or whatever else is that the target operating system doesn’t need to be working yet.

Normal remote desktop:
PC boots
→ OS boots
→ network works
→ remote service starts
→ then I can connect

JetKVM:
press power
→ BIOS appears
→ I'm already there


JetKVM’s current documentation specifically lists BIOS changes, boot failures and fresh operating-system installations as normal use cases. It streams up to 1080p60 video and sends keyboard/mouse input directly to the target machine.

For a test bench, that’s the whole bloody point.

My extremely complicated test bench

Target PC
   │
   ├── video out ─────→ JetKVM HDMI input
   │
   └── USB ───────────→ JetKVM
                         │
                         └── Ethernet / network
                                 ↓
                           my normal PC
                                 ↓
                              browser


That’s basically it.

I leave my normal keyboard, mouse and monitors exactly where they are. The test machine gets JetKVM.

Then I open the JetKVM web interface and suddenly the random PC sitting on the bench is just another browser tab.

DisplayPort target? Watch the adapter direction

One of the machines I wanted to use on the bench only had DisplayPort available, while JetKVM takes HDMI video.

So I grabbed a short adapter/cable rather than rearranging the whole desk again.

Adapter direction matters. For a PC with DisplayPort output going into JetKVM’s HDMI input, you want a cable/adapter intended for DisplayPort source → HDMI display/sink. HDMI-to-DisplayPort adapters are not necessarily reversible, and some conversions need active electronics.

It’s a tiny thing, but nothing improves your mood like buying an adapter that physically fits both ends and electronically does absolutely fuck-all.

Fresh OS installs without a keyboard, mouse or USB stick

This is where JetKVM goes from convenient to genuinely excellent for a test bench.

Its Mount Drive feature can emulate a read-only CD/DVD or disk drive to the target PC, and the virtual media is available during BIOS/UEFI boot.

Current JetKVM supports:

ISO
IMG
QCOW2
WDI
VMDK


Only one image can be mounted at a time, but for installing an OS that’s hardly a problem.

JetKVM currently gives you three ways to provide the image:

  • Storage mount — upload the image to JetKVM first; JetKVM recommends this for the best performance.
  • URL mount — JetKVM streams an image from an HTTP/HTTPS URL.
  • Browser mount — stream the image from the computer running your browser; the browser tab needs to remain open.

So my new fresh-install process can basically be:

Plug test PC into JetKVM
        ↓
Power on
        ↓
Enter BIOS remotely
        ↓
Mount ISO in JetKVM
        ↓
Boot virtual media
        ↓
Install Proxmox / Debian / Windows / whatever
        ↓
Never move my keyboard


Which is considerably nicer than my previous enterprise-grade solution of “where the fuck did I leave that Ventoy SSD?”

PXE booting is nicer too

PXE is another perfect test-bench use case because JetKVM doesn’t care whether the target OS has an IP address yet.

JetKVM only needs its own network connection. The target PC can be sitting in BIOS with no operating system whatsoever.

I can remotely:

Power / boot test PC
        ↓
enter BIOS or boot menu
        ↓
select network / PXE boot
        ↓
watch DHCP + PXE attempt
        ↓
fix whatever I broke
        ↓
try again without leaving chair


That’s useful when I’m testing network booting because the machine I’m troubleshooting obviously can’t run RDP if it currently contains precisely zero operating systems.

Keyboard and mouse are emulated by JetKVM

I originally wondered whether a keyboard and mouse would need to be plugged directly into a PC during a fresh install.

Nope.

JetKVM presents the keyboard/mouse input over USB to the target, so BIOS and installers can see them without drivers from the installed operating system.

It also has a virtual keyboard and a Paste from Host feature, which is extremely handy when an installer wants a hostname or some horrible password that I have no intention of typing manually.

Current JetKVM documentation notes that its keyboard mapping is US layout, so keep that in mind if your local keyboard layout expects symbols in different places.

What about powering the test PC on and off?

For a quick bench test I can obviously just press the case power button like some sort of caveman.

If I want the full remote experience, JetKVM also has an optional ATX Extension Board that provides remote desktop-PC power and reset control.

That makes more sense for a machine that stays connected to JetKVM permanently. For machines constantly arriving and leaving the test bench, physically pressing the power button is hardly the part of the workflow that was ruining my life.

Powering JetKVM itself

The normal setup can power JetKVM from the target computer’s USB-C connection if that port remains powered.

JetKVM also ships with a USB-C power/data splitter so power can come from a separate 5V supply while USB data still goes to the target PC.

For a test bench I actually like separate power because JetKVM stays alive even while I’m shutting the target down, breaking it, rebuilding it and generally treating the machine with the respect test hardware deserves.

Local access is enough for my bench

JetKVM supports optional remote/cloud access, but for a machine sitting a few metres away on my own network I don’t need to make this complicated.

JetKVM’s local web interface is available directly from its IP address, which is shown on the device’s front screen.

Current JetKVM lets you password-protect local access, and I would enable that. Without it, anyone on the same network who knows the IP can access the KVM interface.

A KVM literally gets keyboard/video access to a machine before the operating system has started. Probably not the device to protect with “eh, she’ll be right”.

My actual before and after

Before JetKVM entered my brain:

Need to test PC
→ find spare keyboard
→ find mouse
→ steal monitor cable
→ wrong connector
→ find adapter
→ crawl around desk
→ install OS
→ put everything back
→ repeat next time


After remembering the JetKVM sitting there:

Need to test PC
→ HDMI/DP adapter + USB
→ open browser
→ done


My god.

I was so happy when I realised this that it was immediately followed by the much less flattering realisation that I already owned the bloody thing.

Things JetKVM is now useful for around my homelab

  • Testing random PCs without stealing my desktop peripherals.
  • BIOS/UEFI configuration.
  • Fresh Proxmox, Linux and Windows installations.
  • PXE boot testing.
  • Diagnosing a machine with broken networking.
  • Watching boot errors before SSH/RDP could possibly exist.
  • Recovery environments and bootable ISOs.
  • Temporary access to a headless server that has decided today is the day it stops booting properly.

Basically, anything where my normal reaction used to be:

"sigh... guess I need to plug a monitor into it"


is now a JetKVM job.

Would I use one specifically as a test-bench tool?

Absolutely.

A normal KVM switch is great when the same few computers stay permanently wired to one desk. My problem is different: the computer keeps changing.

JetKVM means the permanent side of the test bench is my browser. Only the target-side HDMI/USB cables move.

And virtual media means even the installation USB stick can fuck off.

Very elegant solution.

Would’ve been even more elegant if I’d remembered I owned it before spending half an hour trying to invent it again 😂.

References

Using a TrueNAS SMB Share as a Steam Library

Status: CURRENT — HOMELAB / UNSUPPORTED-ISH STEAM SETUP
Last reviewed: 31 August 2026
Applies to: TrueNAS SMB shares, Windows Steam clients and a separate Steam updater VM

This is how I keep most of my Steam library on TrueNAS instead of filling every gaming PC with duplicate installs. It works very well for me, but Steam does not officially document one shared SMB library being actively managed by multiple Steam clients as a supported setup. I deliberately use one VM as the updater and keep Steam from starting automatically on my gaming PC to reduce the amount of simultaneous fiddling with the shared library.

I have a lot of Steam games.

Keeping every one of them on fast local NVMe would be lovely, but apparently storage manufacturers still expect money in exchange for 8 TB SSDs. Rude.

So most of my Steam library now lives on a TrueNAS SMB share. My gaming PCs can access the same game files over the network, while I still keep a local NVMe Steam library for the games where I actually care about maximum storage performance.

The part that makes the setup especially useful is a small Windows VM whose entire job is basically:

Steam updater VM
      ↓
Steam stays running
      ↓
Shared TrueNAS library
      ↓
Games update in the background

Gaming PC
      ↓
Steam DOES NOT start with Windows
      ↓
I want to play something
      ↓
Open Steam
      ↓
boom — game is already updated :)


I don’t need Steam sitting on my gaming PC all day downloading patches in the background. The VM babysits that nonsense for me.

Why SMB instead of iSCSI?

I actually tried iSCSI first.

On paper it looked perfect: present storage from TrueNAS as a block device, Windows sees a normal disk, Steam is happy.

The problem is that a normal Windows filesystem such as NTFS expects to own that block device. Having multiple normal Windows machines mount the same iSCSI filesystem at the same time is not what it’s designed for unless you’re using a proper clustered filesystem/setup.

That killed the idea for me because I specifically wanted:

Updater VM
    +
Gaming PC
    +
Sim PC / other clients
    ↓
same game storage


SMB is file sharing, so multiple clients are exactly what it’s for. TrueNAS uses Samba for SMB and supports Windows-style ACLs and multiple users/devices. Much better fit for what I was actually trying to build.

My layout

TrueNAS
└── Steam dataset
    └── SMB share
        ↓
        ├── Updater VM
        ├── Main gaming PC
        └── Other gaming/sim PC

Each Windows machine maps the share
to the same drive letter where possible.


I like using the same drive letter on every Windows machine simply because it removes another variable when troubleshooting.

1. Create a dedicated Steam dataset in TrueNAS

Don’t share the root of the pool. TrueNAS’s current SMB documentation recommends creating a dedicated child dataset for SMB rather than exporting the pool/root dataset itself.

Something like:

tank
└── steam


When creating the dataset, use the SMB dataset preset.

Current TrueNAS uses an NFSv4 ACL for datasets created with the SMB preset. That’s what I want here because the clients are Windows machines and Steam needs normal read/write/traverse behaviour across a massive tree of game directories.

2. Create the SMB user and permissions

Create or use a normal TrueNAS user with Samba Authentication enabled. Don’t use root for SMB access.

Give that user or a dedicated Steam group the access it needs to the dataset.

For my Steam library the clients need to be able to:

  • Read game files.
  • Create/update files.
  • Delete old files during patches.
  • Traverse directories.
  • Write Steam’s appmanifest_*.acf files.

TrueNAS has both a Share ACL and the underlying Filesystem ACL. They’re separate layers. Make sure you aren’t fixing one while the other is still telling Windows to piss off.

The ACL problem I hit

This was the annoying bastard in my setup.

I initially reused storage that had been set up more like a generic Unix dataset. Steam could see the games, but Windows started throwing permission errors when actually trying to launch some game executables.

The tell was that applying the correct SMB ACL recursively made the executables work again.

That proved the network share itself wasn’t the real problem — inherited permissions on the existing files were.

Do not make “apply ACL recursively every time Steam updates” your permanent fix. If new files keep arriving with the wrong permissions, fix the dataset/ACL inheritance properly. I eventually moved the library off, corrected the dataset as a proper SMB dataset, then moved the data back rather than continuing to play permission whack-a-mole forever.

If you’re creating the Steam dataset fresh, doing it correctly from the beginning saves a lot of moving several terabytes back and forth while questioning your life choices.

3. Create the SMB share

In TrueNAS:

Shares
→ Windows (SMB) Shares
→ Add
→ select the Steam dataset
→ enable the SMB service


For example, if the TrueNAS hostname is truenas and the share is called steam:

\\truenas\steam


4. Map it in Windows

On the gaming PC and updater VM, map the SMB share to a drive letter.

For example:

S:  →  \\truenas\steam


You can do that through This PC → Map network drive, or from a command prompt:

net use S: \\truenas\steam /persistent:yes


Windows will use the credentials you supply/store for the TrueNAS SMB account.

I prefer mapping the drive before Steam starts. If Steam launches before Windows can see the network library, it can decide the storage has vanished and your entire library suddenly looks uninstalled. The files are usually still there; Steam is just having a moment.

5. Add the network library to Steam

Current Steam uses the Storage Manager:

Steam
→ Settings
→ Storage
→ Add Drive / +
→ Let me choose another location
→ select S:\SteamLibrary


If the library already contains games, Steam should discover the existing installations once the library folder is added.

Valve’s support documentation also recommends re-adding an existing library through Storage Manager if installed games suddenly appear as uninstalled.

My updater VM is the bit I really like

I have a small Windows VM pointed at the same Steam SMB library.

Steam stays open on that VM and its job is basically to watch the library and keep games patched.

For every game I care about keeping ready, I set:

Game
→ Properties
→ Updates
→ Automatic Updates
→ High Priority - Always auto-update this game before others


Yes, I went through and set the games to High Priority. Yes, it was tedious. No, I don’t particularly want to do it again 😂.

Steam officially describes that setting as keeping the game at high priority so it auto-updates before other games.

The VM isn’t there to play anything. It’s a glorified game-patch babysitter.

Steam does NOT start with my gaming PC

This is the other half of the workflow.

On my actual gaming PC:

Steam
→ Settings
→ Interface
→ Run Steam when my computer starts
→ OFF


Valve still officially provides that startup toggle.

I don’t need the gaming PC sitting there all day checking a library that the VM is already maintaining.

When I actually want to play:

Turn on gaming PC
      ↓
SMB drive maps
      ↓
do normal PC stuff
      ↓
decide I want to play something
      ↓
open Steam
      ↓
shared library appears
      ↓
game is already patched
      ↓
boom :)


That’s the whole reason I like this setup. The updater VM does the boring waiting while the gaming machine only runs Steam when I actually want Steam.

A warning about multiple Steam clients

This is the unsupported-ish part. Steam does not document a single SMB Steam library being concurrently managed by several Steam clients as a supported storage architecture.

The shared directory contains not just the game data but also Steam metadata such as:

steamapps/
├── common/
├── downloading/
├── shadercache/
└── appmanifest_123456.acf


Community reports show that two Steam clients can disagree about or rewrite the shared appmanifest state, making games appear to require another update or disappear/reappear.

My way of reducing that mess is to give the updater VM the update job and not use the gaming PC as another always-running updater.

I also don’t deliberately kick off updates to the same game from two clients at once. Multiple machines trying to be clever with the same files is exactly how a nice simple idea turns into a Sunday afternoon repair job.

Local NVMe still has a job

I don’t force every single game onto the NAS just because I can.

My main gaming PC still has a local NVMe Steam library for FPS games and anything where I want the lowest possible storage latency or where a particular game simply behaves better locally.

Everything else can happily live on the big NAS pool.

That gives me:

Local NVMe
  → shooters / demanding favourites

TrueNAS SMB
  → giant bulk Steam library
  → older games
  → single-player games
  → games I want installed but don't play every day


It’s not about proving a NAS is faster than NVMe — obviously it isn’t. It’s about not buying absurd amounts of local flash storage for games that spend 99% of their life doing absolutely nothing.

What about performance?

My network is 2.5 GbE, so sequential throughput from the NAS is perfectly respectable for a lot of games.

The bigger difference compared with local NVMe is latency and random I/O, not simply the headline link speed.

Some games load a mountain of tiny files and care more than others. If a game feels shit from the network library, I don’t spend three hours trying to win an argument with physics — I move that one back to NVMe.

Steam’s official alternative: Local Network Game Transfers

Valve does have an officially supported feature for moving game installs and update files between Steam machines over the LAN: Local Network Game Transfers.

That’s a really good option if your goal is:

PC 1 already has game
        ↓
PC 2 installs game
        ↓
Steam copies most/all available data over LAN
instead of downloading it again from the internet


My setup solves a different problem. I don’t want another full copy on every machine — I want most of the games to live centrally on the NAS.

If the SMB-library weirdness ever annoys you, Valve’s Local Network Transfer feature is the much more conventional fallback.

If Steam suddenly says every game is uninstalled

First, do not immediately start reinstalling 3 TB of games.

Check:

1. Is S: actually mapped?
2. Can Windows open S:\SteamLibrary?
3. Does S:\SteamLibrary\steamapps still contain the appmanifest files?
4. Does Steam → Settings → Storage still list that library?


If the files are still there but Steam forgot the library, re-add the existing library folder through Storage Manager. Valve documents this as the recovery path for games that appear uninstalled even though the data still exists.

If a game EXE won’t launch from the share

This is where my original TrueNAS ACL problem showed itself.

If Steam can read/download the game but Windows fails opening executables or newly updated files behave differently from older files, inspect the TrueNAS filesystem ACL and inheritance.

For an SMB dataset, make sure the SMB user/group has the intended read/write/traverse permissions and that new files/directories inherit the correct ACL.

TrueNAS’s current SMB documentation distinguishes the SMB Share ACL from the dataset’s Filesystem ACL. Both can affect whether the Windows client actually gets the access you thought you gave it.

Would I do it again?

Yep.

iSCSI was neater when I was thinking about one Windows machine. SMB became the obvious choice once the requirement changed to one large library shared between several machines.

The updater VM is what turns it from a storage experiment into something I actually enjoy using.

VM:
"Don't worry mate, I downloaded the 87 GB patch."

Gaming PC:
"Cheers."

Me:
*opens Steam and plays the game*


No giant local library on every machine, no Steam running on my desktop when I don’t need it, and far less of the “sweet, I have an hour to play — here’s a 46 GB update” experience.

Is it the most normal way to run Steam? Absolutely not.

Does that appear to have stopped me anywhere else on this website? 🙂

References

Authentik Federated Login with Google for SSO

Status: CURRENT
Last reviewed: 31 August 2026
Applies to: authentik with Google OAuth as a federated login source, then authentik SSO to self-hosted applications

In my setup I don’t use a normal authentik password for my everyday account. I authenticate to authentik with Google, then authentik handles the login to the rest of my web apps. One Google login, one authentik session, and most of the homelab stops asking me who I am every five bloody minutes.

What I’m actually doing

This sounds more complicated than it is because identity systems have managed to invent twelve names for “prove who you are”.

The important bit is:

Google
   ↓
federated login
   ↓
authentik
   ↓
SSO / OIDC / SAML / Forward Auth
   ↓
my self-hosted apps


Google is not directly logging me into Plex, Portainer, code-server, Nextcloud or whatever else I’ve decided needs a web interface this week.

Google authenticates me to authentik. Once authentik trusts who I am, authentik becomes the identity provider for the applications behind it.

authentik calls Google a Source. The application side uses authentik Providers.

Google OAuth Source
        ↓
     authentik
        ↓
Proxy / OIDC / SAML Provider
        ↓
    Application


Once I finally understood that direction, the whole thing became a lot less identity-management wizardry and a lot more “oh… that’s actually pretty logical”.

Why I use Google for the authentik login

My everyday authentik account is federated through Google rather than relying on another standalone password that I need to manage.

That gives me Google’s login security at the front door — including whatever MFA/passkey protection I have on the Google account — then authentik handles authorization and SSO for the homelab behind it.

The end result is lovely:

Open app
   ↓
app sends me to authentik
   ↓
already logged into authentik?
   ├── yes → straight back into app
   └── no  → authenticate with Google
                ↓
             authentik
                ↓
             back to app


So after I’ve logged into authentik with Google once, opening the next protected app is normally almost instant. It might bounce through authentik for half a second, but there’s no second password prompt because authentik already has my session.

Google Cloud OAuth or Google Workspace SAML?

authentik currently supports multiple ways to use Google as a federated identity provider.

  • Google Cloud OAuth — the easy option for a personal Google account or normal “Sign in with Google” setup. This is what this guide uses.
  • Google Workspace SAML — useful if you manage a Google Workspace organisation and want the Workspace tenant to act as the identity provider.

For my use case I just want Google authentication into authentik, so OAuth is the simpler path.

1. Create the Google OAuth application

In Google Cloud, create a project for authentik and configure the OAuth consent screen.

Then create an OAuth Client ID with the application type:

Web application


The important bit is the authorized redirect URI.

If my authentik installation was at:

https://auth.example.com


and I use the source slug google, the redirect URI is:

https://auth.example.com/source/oauth/callback/google/


The trailing slash and source slug matter. If the Google redirect URI and authentik source slug don’t match, OAuth will chuck a tantrum instead of politely guessing what you meant.

Google will give you:

  • Client ID
  • Client Secret

Keep the secret secret. Revolutionary advice, I know.

2. Create the Google source in authentik

In the authentik Admin interface go to:

Directory → Federation and Social login → New Source


Select:

Google OAuth Source


Give it something sensible:

Name: Google
Slug: google
Consumer key: <Google Client ID>
Consumer secret: <Google Client Secret>


The slug must match the callback URI we created in Google:

/source/oauth/callback/google/


Save the source.

3. Put Google on the authentik login screen

Creating the source does not automatically mean it appears on the default login page.

Go to:

Flows and Stages
→ Flows
→ default-authentication-flow
→ Stage Bindings
→ default-authentication-identification
→ Edit Stage


Under Source settings, add the Google source to Selected sources.

Now the authentik login page should offer Google as an authentication option.

4. First Google login and account enrollment

The first time you sign in through the Google source, authentik can enroll/create the corresponding authentik user.

Google doesn’t provide a separate traditional username field, so authentik’s default enrollment may ask you to choose one.

If you want new Google users to automatically use their email address as the authentik username, authentik documents an expression policy for the source enrollment flow.

The relevant idea is:

email = request.context["prompt_data"]["email"]
request.context["prompt_data"]["username"] = email
return False


I personally like email as the username here because the account relationship becomes bloody obvious when I’m looking at users later.

My authentik account does not use a normal password

This is the part that made me want federated login in the first place.

For my normal day-to-day account, Google is the login path. I don’t need another password prompt in authentik and then another one in every application behind it.

That means the trust chain is:

Google proves:
"Yep, this is richay"

authentik decides:
"Yep, richay is allowed into this app"

application receives:
"authentik says this is richay"

Everybody stops asking me for another bloody password.


Depending on the application, authentik may protect it with Forward Auth, or the application may use authentik directly through OIDC/SAML.

Forward Auth apps

Forward Auth is useful for applications that either don’t have useful SSO support or where I simply want the reverse proxy to enforce authentication before the request reaches the application.

With Traefik the basic flow is:

Browser
   ↓
Traefik
   ↓
authentik Forward Auth check
   ├── authenticated → app
   └── not authenticated → authentik login → Google if required


authentik currently supports both single-application and domain-level Forward Auth.

  • Single application — each app gets its own authentik application/provider and can have its own policies.
  • Domain level — one Forward Auth provider can cover multiple applications under the same parent domain, but you lose per-application policy control.

I prefer single-application when I care about different permissions. Domain-level is wonderfully lazy when everything under the domain is for the same trusted people.

OIDC / SAML apps

If an application supports proper OIDC or SAML, I generally prefer letting the application talk directly to authentik.

Then the flow is:

Application
    ↓
redirect to authentik
    ↓
authentik session already exists
    ↓
issue OIDC/SAML response
    ↓
application creates its own session


This is still single sign-on even though the application creates its own local session. The important bit is that I didn’t need to type another password.

Why it feels like Google logs me into everything

Technically Google only authenticates me to authentik.

But from my point of view:

1. Login to authentik with Google
2. Open Portainer
3. Logged in
4. Open another protected app
5. Logged in
6. Open another one
7. Still logged in

Me:
"well this is fucking lovely" :)


That’s because the browser already has a valid authentik session. Each application can send me through authentik, authentik recognises me, and the round trip finishes without asking me to authenticate again.

Important: keep a break-glass local admin

Do not make Google your only possible route into authentik administration.

If Google is down, your OAuth app is misconfigured, the client secret expires/gets replaced, DNS is cactus, or you accidentally break the authentik authentication flow, a federated-only admin account can leave you standing outside your own front door.

I keep a separate local administrator/recovery path that is not my everyday account.

Give it a strong unique password, protect it appropriately, don’t use it for normal browsing, and keep the details somewhere safe.

authentik also has a recovery-key mechanism if you’ve managed to lock yourself out. For a Docker Compose deployment, from the authentik Compose directory the current recovery command is:

docker compose run --rm server create_recovery_key 10 akadmin


That produces a recovery link valid for the specified number of minutes — ten in the example above.

Treat that URL like a password. Anyone holding it gets direct access as the selected authentik user while the key is valid.

Google account security now matters a lot

The convenience comes with an obvious trade-off: Google is now the first link in the authentication chain.

If someone compromises that Google account, they may also gain the ability to authenticate to authentik and therefore reach applications that account is authorised to use.

So I absolutely want strong MFA/passkeys and good account-recovery settings on the Google side. SSO is brilliant, but it also means the one key you’re carrying opens more doors.

This does not automatically give every Google user access

Federating Google into authentik and authorising applications are separate jobs.

authentik can bind policies to sources and applications, use groups, Grants or other policy logic to decide who actually gets access.

If you’re using a public Google OAuth application, don’t assume “has a Google account” should equal “welcome to my Proxmox dashboard, stranger”. That would be a fairly spectacular own goal.

Logging out

Because there are multiple sessions involved, logout can be slightly less magical than login.

  • Logging out of an individual application may only destroy that application’s session.
  • The authentik browser session may still exist, so revisiting the app can log you straight back in.
  • Logging out of authentik doesn’t necessarily sign you out of Google itself.

That’s not a bug — Google, authentik and the application are separate layers. SSO makes them feel like one thing right up until logout reminds you there are actually three sets of cookies having a meeting behind the scenes.

My end result

Google account
     ↓
authentik Google OAuth Source
     ↓
authentik session
     ↓
┌──────────────┬──────────────┬──────────────┐
│ Forward Auth │     OIDC     │     SAML     │
└──────────────┴──────────────┴──────────────┘
     ↓               ↓               ↓
self-hosted apps, without another pile of passwords


I log into authentik with Google, then authentik handles my identity for the rest of the web apps. The apps don’t need my Google password, Google doesn’t need to know about every random container in my homelab, and I get proper SSO without maintaining another password for every bloody service.

One front door, lots of rooms. Just make sure you’ve hidden a spare key somewhere sensible before welding the other doors shut 🙂

References

Mounting NAS Shares in LXC Containers on Proxmox

Status: CURRENT
Last reviewed: 31 August 2026
Applies to: Proxmox VE 8/9, LXC containers, host-mounted SMB/CIFS or NFS shares

This is the clean way I mount NAS storage into my Proxmox LXC containers: mount the network share once on the Proxmox host, then pass that host directory into whichever containers need it using a Proxmox bind mount.

When I rebuilt my Proxmox setup, one of the first things I needed was access to the same NAS shares from multiple LXC containers.

You can try mounting SMB/NFS directly inside each container, but I much prefer doing it once on the Proxmox host and then bind-mounting the result into the LXCs.

That gives me one place for NAS credentials and mount behaviour, while each container just sees a normal Linux directory. Fewer moving parts, fewer opportunities for something to go cactus.

The layout is basically:

NAS / SMB / NFS
       ↓
Proxmox host mounts share
       ↓
/mnt/nas/media
       ↓
Proxmox bind mount
       ↓
LXC sees /mnt/media
       ↓
Plex / Docker / whatever needs it


1. Mount the share on the Proxmox host first

The network share needs to be working on the Proxmox host before we pass it into the container.

For SMB/CIFS I use /etc/fstab. I have a separate guide for that here:

Mount a Windows / SMB Share in Linux with fstab

For this guide I’ll assume the NAS share is already mounted on the Proxmox host at:

/mnt/nas/media


Before touching the LXC, confirm that path really is the network mount and not just an empty local directory:

findmnt -T /mnt/nas/media
ls -lah /mnt/nas/media


This check matters. If the NAS mount is broken but the empty local mount-point directory still exists, you can accidentally bind that empty directory into the LXC. Worse, an application may start writing into the Proxmox host’s local disk thinking it’s writing to the NAS. That’s a fun one to discover after the root filesystem fills up 😅.

2. Pick where the share should appear inside the LXC

The host path and container path do not need to be the same.

For example:

Proxmox host:
  /mnt/nas/media

Inside LXC:
  /mnt/media


Personally I like keeping container paths simple. The LXC doesn’t need to know that the files came from some NAS sitting elsewhere on the network.

3. Stop the LXC

I’ll use container 103 in the examples:

pct stop 103


4. Add the bind mount with pct

Proxmox officially supports bind mounts for exposing arbitrary host directories inside an LXC.

Add the NAS path as the first mount point:

pct set 103 -mp0 /mnt/nas/media,mp=/mnt/media


Check the resulting container configuration:

pct config 103


You should see something similar to:

mp0: /mnt/nas/media,mp=/mnt/media


Under the hood that lives in:

/etc/pve/lxc/103.conf


You can edit the config directly, but I prefer pct set for something this simple. Less chance of fat-fingering the container config because I’ve decided I suddenly know better than the tooling.

Read-only mount for media

If the container only needs to read the files — a Plex container is a good example — make the bind mount read-only:

pct set 103 -mp0 /mnt/nas/media,mp=/mnt/media,ro=1


If Plex can stream the media without needing to modify it, there is no bloody reason to give it write access to the whole library.

5. Start the container and test it

pct start 103
pct exec 103 -- ls -lah /mnt/media


Or enter the container normally:

pct enter 103
ls -lah /mnt/media


If you can see the NAS files, the actual bind mount is done.

Unprivileged LXC permissions

This is normally the bit where a perfectly good mount suddenly becomes Permission denied.

Proxmox creates new LXCs as unprivileged containers by default. That’s a good thing for security, but it means UIDs inside the container are mapped to different unprivileged UIDs on the Proxmox host.

With the usual default Proxmox mapping:

LXC UID 0      → host UID 100000
LXC UID 1000   → host UID 101000
LXC UID 1001   → host UID 101001
...


So if an application runs as UID 1000 inside an unprivileged LXC, the bind-mounted files need to be accessible to the corresponding mapped host UID — normally 101000 with the default map.

Check whether the container is unprivileged:

pct config 103 | grep -E 'unprivileged|lxc.idmap'


Don’t blindly assume the 100000 offset if you’ve configured custom ID mappings. The example above is the normal/default arrangement. Custom lxc.idmap entries change the maths.

If the host share is SMB/CIFS

This is actually handy because CIFS lets you control the UID/GID that Linux presents for the mounted files.

For example, if the application needs UID/GID 1000:1000 inside an unprivileged LXC using the default map, the host CIFS mount can present the files as:

uid=101000,gid=101000


An fstab mount might therefore contain:

//NAS/media /mnt/nas/media cifs credentials=/etc/samba/credentials-nas,uid=101000,gid=101000,file_mode=0660,dir_mode=0770,nofail,_netdev,x-systemd.automount 0 0


The NAS still controls the real SMB permissions. These UID/GID options just control how the files appear to Linux on the Proxmox host and therefore through the bind mount.

If the host share is NFS

NFS permissions are much more directly tied to numeric UID/GID values supplied by the NFS server, so make sure the IDs exported by the NAS line up with what the mapped LXC user needs.

This is one reason SMB can sometimes be easier for a simple media bind mount: I can explicitly choose the host-side presented UID/GID in the CIFS mount options.

Privileged containers are easier — but that’s not a reason to use one

In a privileged LXC, UID 1000 inside generally corresponds directly to UID 1000 on the host, which makes bind-mount permissions simpler.

That does not mean I’d convert a container to privileged just to make a NAS mount easier. Proxmox explicitly considers unprivileged containers the safer design.

Fix the ID/permission mapping rather than trading away container isolation because Linux permissions hurt your feelings for ten minutes 🙂

Multiple shares

You can add more mount points as mp1, mp2, etc.

pct set 103 -mp0 /mnt/nas/media,mp=/mnt/media,ro=1
pct set 103 -mp1 /mnt/nas/downloads,mp=/mnt/downloads
pct set 103 -mp2 /mnt/nas/backups,mp=/mnt/backups


That is essentially how I like to build service LXCs: give each one only the bits of storage it actually needs rather than mounting the entire NAS because apparently restraint is illegal.

Bind mounts are NOT included in normal LXC backups

Important: Proxmox does not include the contents of bind-mounted host directories in normal vzdump container backups.

For a 40 TB media share this is normally exactly what I want. Backing up a tiny Plex LXC should not suddenly decide it also needs to copy the entire bloody NAS.

But it means your backup plan needs to treat these as two separate things:

LXC backup
  → container OS/config/application data

NAS backup
  → the actual files inside the bind-mounted share


Don’t restore an LXC backup six months later and wonder why 20 TB of movies didn’t magically emerge from it.

Snapshots are the same story

Bind mounts are not managed container storage, so their contents are not part of normal Proxmox LXC snapshots either.

The NAS remains responsible for its own snapshots, ZFS protection, replication, backups, prayers to the storage gods, etc.

Don’t bind-mount random host system directories

Proxmox specifically warns against bind-mounting things such as /, /var or /etc into containers.

Use dedicated source directories for the data you’re intentionally sharing.

Also, the bind-mount source path must not contain symlinks.

A nice boring path such as:

/mnt/nas/media


is exactly what we want. Boring storage paths are good storage paths.

NAS unavailable when Proxmox boots

This deserves a mention because network storage occasionally decides it wants a sleep-in.

On the Proxmox host I use network-friendly fstab options such as:

nofail,_netdev,x-systemd.automount


The automount means the network share is mounted when the path is first accessed rather than forcing Proxmox to sit there during boot waiting for the NAS.

Before manually starting a container after a NAS outage, I still check:

ls /mnt/nas/media >/dev/null
findmnt -T /mnt/nas/media


If that doesn’t show the expected CIFS/NFS mount, fix the host mount first. Don’t let the container happily write into an empty local directory underneath the missing NAS mount. That’s how mystery disk usage is born.

Remove a bind mount

Stop the container first:

pct stop 103
pct set 103 -delete mp0


Then confirm:

pct config 103


My preferred setup

For NAS-backed LXC services I now keep the design pretty boring:

NAS
 ↓
SMB/NFS mounted once on Proxmox
 ↓
Dedicated /mnt/nas/... directories
 ↓
Proxmox mp0/mp1 bind mounts
 ↓
Unprivileged LXCs
 ↓
Read-only wherever possible


No SMB passwords copied into every container, no privileged LXC just because UID mapping was annoying, and no 40 TB surprise hiding inside a vzdump job.

Mount it once, pass through what each container needs, then leave the bastard alone while it’s working 🙂

References