Suranyami

Polyglot developer, geometric tessellation fan, ambient DJ.

My rock4 — a Radxa RockPi4 running DietPi with four SATA SSDs on a Penta HAT — has never rebooted cleanly. For as long as I've had it in the rack, issuing sudo shutdown -r now meant walking over to the machine, waiting ten minutes to confirm it was definitely stuck, and flipping the power switch. Every single time.

It worked perfectly otherwise. Services ran fine. Drives mounted fine. The machine was solid right up until the moment you asked it to restart.

This is the story of finding the actual cause — and why the fix I thought would work made no difference at all.


The obvious culprit (that wasn't)

When you have a server that hangs on shutdown, the usual suspects are slow-stopping services, or so I was led to believe. The systemd-analyze blame output on rock4 had an obvious candidate: unattended-upgrades.service, which by default gets a TimeoutStopSec of 1800 seconds — 30 minutes. If an apt upgrade happened to be running at shutdown time, systemd would sit there for half an hour waiting for it to finish before giving up.

I applied a drop-in to cap it at 5 minutes. It still hung. For over two hours.

I dug deeper and found a second culprit: apt-daily-upgrade.service, a separate timer-triggered unit that calls unattended-upgrades. It has its own TimeoutStopSec of 900 seconds. I capped that too.

Still hung.

At this point I was fairly sure the apt theory was wrong, but I didn't have a better one yet.


The diagnostic that changed everything

Here's the thing about a “hung” server: it's worth checking whether the machine is actually dead or just systemd that's stuck.

After triggering a shutdown and watching rock4 go dark, I opened LanScan and scanned the local network. rock4 was still there. Still responding to pings. Port 111 (rpcbind) still open.

That's not a dead machine. That's a machine with a live kernel where systemd has frozen mid-shutdown.

systemd shuts down in phases, supposedly: it stops services, then unmounts filesystems, then hands off to the kernel for the actual reboot. If it gets stuck at the filesystem unmount step, the kernel never gets the reboot signal — the machine just idles there indefinitely, still on the network, lights still on, going nowhere.

The question was: which mount was blocking?

rock4 has four local SATA drives and one NFS mount — /mnt/media, served from my itx machine over the local network. I pulled up the running containers:

docker inspect jackett --format '{{ json .Mounts }}'

There it was:

/mnt/media/media/Downloads → /downloads

jackett — my torrent indexer — had an NFS-backed path bound as a Docker volume.


Why this hangs forever

When Docker mounts a volume into a container, the kernel creates a bind mount that keeps a reference count on that filesystem. Even after Docker stops the container, the overlay filesystem machinery can retain a reference to the underlying mountpoint.

So when systemd later runs umount /mnt/media, the kernel sees that something still holds a reference to that mount and returns EBUSY. Systemd retries. The NFS server is still up, healthy, and reachable — but that doesn't matter. The umount call isn't failing because the server is gone; it's failing because the local kernel thinks something still has the filesystem open.

And here's the critical part: umount has no timeout. The TimeoutStopSec settings on services don't help. The soft,timeo=30 NFS mount option doesn't help — that governs read/write operation timeouts, not the unmount syscall itself. Without something explicitly forcing a lazy unmount, systemd will wait forever.


The fix

jackett is a torrent indexer. It speaks to tracker APIs and returns search results to Radarr and Sonarr. It does not need to read or write files on disk. The downloads volume was there because at some point, someone (me, almost certainly) copy-pasted a docker-compose snippet from the internet without thinking about whether every line was necessary.

The fix was removing one line from services/jackett.yml:

# Before
volumes:
  - /bricks/rock4-2/jackett:/config
  - /mnt/media/media/Downloads:/downloads  # ← this line

# After
volumes:
  - /bricks/rock4-2/jackett:/config

Redeployed jackett, issued sudo shutdown -r now, and watched. Three minutes later, rock4 was back online. No power cycle. First clean reboot in years.


The general rule

If you're running Docker containers on a machine that also has NFS mounts, think hard before binding any NFS-backed path into a container volume. The risk isn't that Docker will do something wrong — it's that the combination of Docker's bind mount lifecycle and the kernel's umount semantics creates a window where shutdown can hang indefinitely with no error message and no timeout.

If you genuinely need an NFS path inside a container, the belt-and-suspenders fix is to add x-systemd.mount-timeout=30 to the relevant fstab entry. This caps the mount's teardown time at 30 seconds rather than forever — not ideal, but it bounds the hang.

itx.local:/mnt/media  /mnt/media  nfs  soft,timeo=30,x-systemd.mount-timeout=30  0  0

But better is to audit your container volume mounts and ask: does this service actually need filesystem access, or is it just inheriting a volume that was copy-pasted into the config at some point?


Why it was so hard to diagnose

A few things made this particularly hard to spot:

No error message. The machine doesn't log “stuck waiting for NFS umount.” It just sits there. Systemd is doing exactly what it's supposed to do: retrying an unmount that keeps returning EBUSY. There's nothing in the journal because journald itself has already stopped by the time the hang happens.

The wrong hypothesis was plausible. Unattended-upgrades with a 1800s timeout genuinely can cause shutdown hangs. Capping it was the right thing to do regardless. It just wasn't the root cause here.

The symptom was intermittent enough to seem random. Sometimes rock4 rebooted. When the NFS server (itx) was down or the jackett container had been recently restarted, Docker might have already released the reference by the time shutdown reached the umount step. This made it feel like a timing issue rather than a deterministic one.

The diagnostic breakthrough — checking whether the machine was still pingable after it “hung” — was the key. A dead machine and a machine stuck mid-shutdown look identical from across the room. They look very different from a network scanner.


The problem is probably older than NFS

After fixing the hang, I realised something. rock4 ran GlusterFS for years before the NFS migration — a distributed filesystem where each node contributes “brick” drives to a replicated pool. The containers on rock4 mounted GlusterFS paths like /mnt/storage/jackett, and those mounts have the same property as NFS: they're network-backed filesystems that can't unmount cleanly while something holds a kernel reference to them.

GlusterFS uses FUSE (Filesystem in Userspace) to expose its mounts locally. FUSE unmounts are actually harder to complete cleanly than NFS: to release a GlusterFS FUSE mount, the glusterd daemon has to coordinate across the network, consult its peers, and tear down brick connections in order. If Docker is still holding a reference to the mountpoint, glusterd can't complete that teardown, and umount returns EBUSY — the same outcome as NFS, but with more moving parts and more ways to stall.

So the sequence was almost certainly: Docker container with GlusterFS volume → indefinite hang → GlusterFS decommissioned → NFS mounted → same container config carried across with updated paths → Docker container with NFS volume → still hangs.

Different filesystem, identical mechanism, years of continuity. The jackett config probably got its downloads volume added once, years ago, and nobody thought to question it during the storage migration.

The GlusterFS angle matters beyond this one machine. Between roughly 2018 and 2022, GlusterFS was enormously popular in self-hosted circles — TrueNAS Scale shipped it as the default clustered storage backend, and countless homelab builds adopted it for redundant storage across a few nodes. Many of those setups ran Docker containers with GlusterFS-backed volumes. Many of those setups probably had machines that wouldn't reboot cleanly. It's a reasonable bet that a lot of those people never connected the reboot hang to the storage layer.

RedHat deprecated GlusterFS in RHEL 9 (announced 2022). The official framing was “focus on other storage solutions,” but the operational complexity was a significant part of the story: GlusterFS was difficult to run at small scale, prone to split-brain, and had long-running issues with graceful shutdown and FUSE lifecycle management. The Docker reboot hang described here is a concrete example of that class of problem — the kind of subtle, hard-to-diagnose operational failure that accumulates over time and eventually makes a piece of software too difficult to maintain and recommend.

If you ran GlusterFS and your server never quite rebooted cleanly: this was probably why.


Setup

  • rock4: Radxa RockPi4, DietPi (Armbian kernel 6.18), 4× 3.6TB SATA SSDs via Penta HAT
  • itx: Rock 5 ITX, NFS server, mergerfs pool at /mnt/media
  • Container management: uncloud
  • jackett: lscr.io/linuxserver/jackett

Discuss...

Just watched this:

https://www.imdb.com/title/tt0460791/?ref_=nv_sr_srsg_6_tt_8_nm_0_in_0_q_the%20fall

“The Fall”, by director Tarsem Singh.

Outstandingly beautiful visuals. Like watching a graphic novel by Möbius brought to life. An opiate-filled fever-dream of over-the-top sensations for the pure sake of it.

Simply incredible.

Discuss...

So, uncloud is obviously still a work in progress, so bugs will happen. Because I've drunk the cool-aid and dived into using it on my homelab, I'm doing my best to be a good netizen and helping troubleshoot any issues I find.

Today, I had a great session with the author, where my cluster was unresponsive, except for running commands like uc machine ls. The services were still running, but I'd lost the ability to perform new deploys or check the state of services. Seems like a bug, and might be related to WireGuard + Tailscale + Uncloud and IP6 addresses.

Along the way, though, I learned a new command, which got me completely unblocked:

uc ctx conn

Run this and you get a list of all the machines in your cluster and you can choose a different default machine to proxy traffic through. In my case, it seems like there's something wrong with only the Ubuntu machines I have… the others on DietPi work just fine.

One caveat I had, though: because my machines are behind NAT on a home network, I have ports 80 and 443 redirected to the “default machine”, so I had to change that redirection to point to the local IP address of the new default machine, then do a new deploy of whatever services I needed to update. I believe the issue here is probably to do with registering the Let's Encrypt certificate… they require the machine to be discoverable for that to complete.

Discuss...

This is my docker-compose.yaml for beszel:

services:
  beszel:
    image: henrygd/beszel:latest
    x-ports:
      - beszel.your-domain.com:8090/https
    volumes:
      - ./beszel_data:/beszel_data
      - ./beszel_socket:/beszel_socket
  • Deploy the beszel webapp with uc deploy bezel.yml
  • Signup and login
  • Go to settings/tokens and activate “Universal Token”
  • Under the ••• drop-down menu, select “Copy Docker Compose”. This will give you something like this:
services:
  beszel-agent:
    image: henrygd/beszel-agent
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./beszel_agent_data:/var/lib/beszel-agent
      # monitor other disks / partitions by mounting a folder in /extra-filesystems
      # - /mnt/disk/.beszel:/extra-filesystems/sda1:ro
    environment:
      LISTEN: 45876
      KEY: 'ssh-ed25519 xxxxxxxxxxxxxxxxxxxxxxxxx'
      TOKEN: xxxx-xxxxx-xxxxx-xxxxx
      HUB_URL: https://beszel.your-domain.com

Add this line to the bottom of it:

    deploy:
      mode: global

This will ensure that the agent is installed on all your machines.

I usually just paste the beszel-agent bit into the first docker-compose, then re-run:

uc deploy -f beszel.yml

This will give you some output like this:

[+] Deploying services 8/8
 ✔ Container beszel-agent-xmai on eon    Started         1.4s 
 ✔ Container beszel-agent-os6i on itx    Started         0.6s 
 ✔ Container beszel-agent-hkhd on node2  Started         0.6s 
 ✔ Container beszel-agent-w84p on node3  Started         1.4s 
 ✔ Container beszel-agent-qd42 on node4  Started         0.6s 
 ✔ Container beszel-agent-c79q on pico   Started         0.5s 
 ✔ Container beszel-agent-v7ff on rock4  Started         0.8s 
 ✔ Container beszel-agent-odec on rock5  Started         0.7s 

Then you might want to rename the nodes in the beszel web UI for easier machine identification. I still haven't worked out how to make that process automatic, but it's not a big deal.

Discuss...

Just before Christmas, I spent an hour or so setting up uncloud on my homelab, and I am stunned at how easy it was to get working.

The motivation for doing this is because I’ve known for a long time that Swarmpit is basically abandoned. Disappointing, but true. The latest release of DietPi, my preferred distro for my Raspberry Pi and RockChip SBCs, included an update to docker and docker-compose that completely broke all operability with Swarmpit. Queue panicked hunting for alternatives and a fortuitous discovery of Uncloud

Here's how I set it up.

DNS

  • Added a wildcard CNAME DNS record pointing *.suranyami.com to my dynamic DNS address: suranyami.duckdns.org.

Tailscale

  • Installed tailscale on each of the machines (Installation Instructions)
  • Because I'm using DietPi, the recommended way to install tailscale is using dietpi-software.
  • Connect each machine to my free tailnet (free tier allows up to 100 nodes) using sudo tailscale up and following the on-screen instructions.

This gives me a stable URL for each individual machine that I can SSH into without needing to do NAT redirection on the router. For instance, my machine called node1 is available to me (and only me) at ssh dietpi@node1.tailxxxxx.ts.net.

SSH config

  • Updated my ~/.ssh/config with entries for all the machines that look like this:
Host node1
  Hostname node1.tailxxxxx.ts.net
  User dietpi

Uncloud

  • Installed uncloud on my laptop: curl -fsS https://get.uncloud.run/install.sh | sh
  • Initialized the cluster by picking one of the above machines as a first server: uc machine init dietpi@node1.tailxxxxx.ts.net --name node1

NAT port redirection

  • Because all my machines are behind NAT, I configured my router to map ports 80 and 443 to point to the above machine. This can ultimately be any of the machines configured after this. The important point here is that at least one of the machines running the caddy reverse-proxy that uncloud installs, needs to be receiving ports 80 and 443 from the outside world.

Add more machines

  • Add other machines using uc machine add dietpi@node2.tailxxxxx.ts.net --name node2

Deploy services

  • Deploy services using uc deploy -f plex.yml where plex.yml is a subset of a docker-compose file, but with minor changes. For instance, to deploy to a specific machine (which I have to do because I need to redirect port 32400 from the router to a specific machine, because plex is annoying like that), I do this:
services:
plex:
  image: linuxserver/plex:arm64v8-latest
# ...
  x-machines:
    - node2
  x-ports:
    - 32400:32400@host
    - plex.suranyami.com:32400/https

And that's about it. No manual reverse-proxy configuration, no manual entry of IP addresses, everything is just automatically given a letsencrypt SSL certificate and load-balanced to wherever the servers are running.

This is honestly the easiest way to self-host anything I've found.

It's been 2 weeks or so now, and now that I've got the knack of the x-ports port-mapping syntax, I've also managed to get all my other services running everywhere.

Notable edge cases were:

Minecraft

x-ports:
  - 25565:25565@host

Plex

x-ports:
  - 32400:32400@host
  - plex.suranyami.com:32400/https

Needed 2 mappings, one for the internal subnet for use by the AppleTV, because of some idiosyncrasy of the way the native Plex app works with behind the NAT versus over t'interwebz.

Jellyfin

x-ports:
    - 1900:1900@host
    - 7359:7359@host
    - jellyfin.suranyami.com:8096/https

Only outages I've had so far were purely hardware-related: robo-vacuum somehow knocked out a power cord that was already loose… derp. That won't happen again. And, the fan software wasn't installed on my RockPi 4 NAS box, so it overheated and shut down. Fixed that this morning.

global deployment

I'm currently using Netdata to monitor my nodes. It's WAY overkill for what I'm running, but hey, whatever. For this we need to do a global deployment:

services:
  netdata:
    image: netdata/netdata:latest
    hostname: "{{.Node.Hostname}}"
# ...
    volumes:
# ...
      - /etc/hostname:/host/etc/hostname:ro
    deploy:
      mode: global

This is essentially the same as a normal docker-swarm compose file, but because it's not actually docker-swarm, this line is a hack to get the hostname: - /etc/hostname:/host/etc/hostname:ro.

There is also a quirk that (hopefully) might be fixed in future versions of uncloud: the volumes don't get created automatically on each machine. For that I had to execute a bunch of uc volume create commands like this:

c volume create netdataconfig -m node2
uc volume create netdataconfig -m node3
uc volume create netdataconfig -m node4
uc volume create netdatalib -m node2
uc volume create netdatalib -m node3
uc volume create netdatalib -m node4
uc volume create netdatacache -m node2
uc volume create netdatacache -m node3
uc volume create netdatacache -m node4

Replicated deployment

One very nice feature is replicated deployment with automatic load balancing. There's not a lot of documentation about how it works at the moment, so I'm a bit suss on it, but essentially it looks like this in the compose file:

    deploy:
      mode: replicated
      replicas: 4

This will cause it to pick a random set of machines and deploy a container on each, and load-balance incoming requests.

There are caveats to this, of course. The service configuration will need to be on a shared volume, for instance, and some services do NOT behave well in this situation. plex is the worst example of this… if you store its configuration, caches and DB on a shared volume, you are gonna have a very bad time indeed because of race-conditions, non-atomicity, file corruption etc.

Which is a shame, because Plex is the service I'd most like to be replicated. I dunno what the solution is. Use something other than Plex seems like the most obvious answer, but as far as I know the alternatives have the same issue.

Discuss...

  1. Oil, filtered from the previous deep fry, repackaged in a Nikka whisky bottle, because they’re cute, and Nikka whisky is frikkin’ fantastic, so of course we’ve got some old bottles lying around.

  2. Soya sauce, decanted daintily into a maple syrup bottle, which is totally not at all confusing some times.

  3. Salt and pepper shakers that are big and chunky and sit in a really crappy wooden base I made to stop them falling over all the time because I’m the clumsiest person I know.

  4. HomePod, playing bleepy noises. The other half of the stereo pair is on the other side of the kitchen, because stereo separation is important and don’t lecture me about how speakers are arranged. Tonight there was a decent selection featuring Wolfram Spyra, Space Frogs, International People’s Gang, Woob, Si Begg, Basement Jaxx, Tosca, and a fun Grimes track from before she went a bit mental after hanging out with mister nazi-baby-maker.

  5. The handle of my Chinese cleaver, the knife I use for literally everything.

  6. The handles of 2 quite nice Global brand knives that were a wedding present from a family member. These are very good Japanese knives. The other knives (and these) are all hanging from the magnetic knife holder I installed.

  7. Preserved lemons. Gotta start using them now, because it’s been long enough. Better decant some into smaller bottles to give away. Funny that I don’t have any Moroccan cook books. Probably still traumatized by “that event in Morocco” 25+ years ago.

  8. Garlic oil. Such an amazingly simple thing to make, and you end up with 2 x awesome things. Chop up garlic. Fry in oil till crispy. Scoop out crispy garlic and use as topping. Add garlic-infused oil to literally anything to make it taste amazing.

  9. Super-cheap oil spray thingy from Aldi. Think it was $10. So useful.

  10. Rosemary-salt. In a blender, combine rosemary, salt crystals, lemon rind, peppercorns. Whizz. Zero to hero.

  11. Left-over Sichuan pepper-salt. Grind Sichuan peppercorns with salt. Left over from making 白切鸡, bái qiē jī, “white-cooked chicken”, then shallow-frying half the chicken the following day for a lovely crispy-skin experience.

Discuss...

Sometimes, I just want to know “what's the IP address” of a machine, and I don't want to see every damn network adapter: loopback, virtual, wireless or wired…

Here's the wall of text that ip addr typically returns on a Raspberry Pi running various docker containers:

dietpi@eon:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether e4:5f:01:67:d8:10 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.238/24 brd 192.168.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2403:5811:6ed:0:e65f:1ff:fe67:d810/64 scope global dynamic mngtmpaddr 
       valid_lft 86212sec preferred_lft 14212sec
    inet6 fe80::e65f:1ff:fe67:d810/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:7f:98:38:dc brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
4: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:69:e3:50:d0 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global docker_gwbridge
       valid_lft forever preferred_lft forever
    inet6 fe80::42:69ff:fee3:50d0/64 scope link 
       valid_lft forever preferred_lft forever
1034: veth5012f18@if1033: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 0a:a4:4f:99:a3:ca brd ff:ff:ff:ff:ff:ff link-netnsid 21
    inet6 fe80::8a4:4fff:fe99:a3ca/64 scope link 
       valid_lft forever preferred_lft forever
1038: veth0a22673@if1037: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 5a:a6:f4:85:ad:01 brd ff:ff:ff:ff:ff:ff link-netnsid 22
    inet6 fe80::58a6:f4ff:fe85:ad01/64 scope link 
       valid_lft forever preferred_lft forever
1040: veth7324567@if1039: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 06:7a:fc:bc:ba:3b brd ff:ff:ff:ff:ff:ff link-netnsid 24
    inet6 fe80::47a:fcff:febc:ba3b/64 scope link 
       valid_lft forever preferred_lft forever
1062: vethf34c1d6@if1061: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether be:97:31:ce:d1:20 brd ff:ff:ff:ff:ff:ff link-netnsid 27
    inet6 fe80::bc97:31ff:fece:d120/64 scope link 
       valid_lft forever preferred_lft forever
1068: vethd64996a@if1067: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 2e:10:e5:36:24:cc brd ff:ff:ff:ff:ff:ff link-netnsid 6
    inet6 fe80::2c10:e5ff:fe36:24cc/64 scope link 
       valid_lft forever preferred_lft forever
976: veth5190b0e@if975: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 06:bb:39:60:81:26 brd ff:ff:ff:ff:ff:ff link-netnsid 5
    inet6 fe80::4bb:39ff:fe60:8126/64 scope link 
       valid_lft forever preferred_lft forever
984: veth88f88fe@if983: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 66:cb:ec:91:57:68 brd ff:ff:ff:ff:ff:ff link-netnsid 9
    inet6 fe80::64cb:ecff:fe91:5768/64 scope link 
       valid_lft forever preferred_lft forever
1007: veth49e1be0@if1006: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether fa:4d:cc:02:3c:6a brd ff:ff:ff:ff:ff:ff link-netnsid 14
    inet6 fe80::f84d:ccff:fe02:3c6a/64 scope link 
       valid_lft forever preferred_lft forever
1016: veth7a3a81d@if1015: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 36:75:87:35:42:7f brd ff:ff:ff:ff:ff:ff link-netnsid 18
    inet6 fe80::3475:87ff:fe35:427f/64 scope link 
       valid_lft forever preferred_lft forever
1022: vetha80dc19@if1021: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default 
    link/ether 72:d5:af:ce:a5:6c brd ff:ff:ff:ff:ff:ff link-netnsid 20
    inet6 fe80::70d5:afff:fece:a56c/64 scope link 
       valid_lft forever preferred_lft forever

So, what's the alternative?

These worked for me:

hostname -I | awk '{print $1}'

# ...
$ hostname -I | awk '{print $1}'
192.168.1.238

or using the results from a DNS query:

ip route get 8.8.8.8 | awk '{print $7}'

# ...
$ ip route get 8.8.8.8 | awk '{print $7}'
192.168.1.238

Discuss...

These last few years, I've been trying out a Kamado Joe smoker.

It's been challenging.

There are a number of things that make them completely different to a normal barbecue. I previously had a simple Weber barbecue. It worked fine for hot and fast cooking and after a bit of burn-down and ash-over, you could easily do a few low & slow things as long as you kept your expectations within an hour or two. There was no thermal insulation to speak of, except for a dome of steel with an adjustable outlet.

A smoker of the sort similar to a Kamado Joe (there are plenty of them…) is a different animal all together.

The first thing to know is this: refractory bricks stay hot for up to 24 hours.

The insides of these types of smokers are modelled similarly to the best pizza ovens and metal refineries. Heat dissipates, and that's bad, if you need to make something hot. It means you need to keep supplying fuel to keep things hot.

That's where refractory bricks come in: they reflect heat back from where they came from.

The outside of a Kamado Joe needs to be running for quite some time before it even gets slightly hot, because of these bricks.

This presents 2 problems:

  1. If I want something to cook low and slow for a long time, I need lots of fuel.
  2. If I ignite a lot of fuel, everything will get very hot and generally have a texture like leather.

So what's the solution?

I found 2 helpful procedures that, after experimenting with them, I can verify they do help make things a lot easier:

  1. The “minion method”. This entails making a semi-circular chain of charcoal with smoking chunks positioned above each section, such that when lit at one end, it slowly burns through the entire chain, a bit like a very slow fuse.

  2. A tray of water. This is so obvious in retrospect! Ideally, the internal temperature should be in the 120-150°C range. Water will have a beneficial feedback effect: too hot and the steam will suppress the burning, too low and it won't do much except act as thermal inertia.

A combination of the 2 above hints, allowing the first burn to properly ash over, and not moving the air inlets/outlets too chaotically has led to much more predictable outcomes.

Today's bounty was:

  • Totally bodaciously tender pork shoulder with brown sugar, mustard and left-over Nong-Shim Ramyun spicy instant noodle powder rub.
  • Tender-as roast chicken from the last 2 hours of the above pork cook, prepped with lemon zest, smoked paprika and oregano rub with olive oil and smoked salt.
  • Purple Japanese sweet potatoes wrapped in foil with olive oil, pepper and salt.
  • Smoked long, sweet peppers
  • Baked and smoked aubergine.

One of the things that made all of the above much, much easier was having a decent temperature probe. I have a Meater probe, and it's worth every cent.

Discuss...

Create a network for nginx-proxy-manager to use:

docker network create nginx-proxy-manager-network

Then add this to all the compose files that want to be referenced by Nginx-Proxy-Manager:

networks:
  nginx-proxy-manager-network:
    external: true

Now you can use the service name as the host in the Nginx-Proxy-Manager GUI:

Source Destination
awwesome.suranyami.com http://awwesome:8088

Discuss...

Enter your email to subscribe to updates.