Recently I’ve done a lot of reorganization with TrueNAS, migrating Plex and Gitea to TrueNAS-based virtual machines and just generally spending a bunch of time poking around with my home lab and improving things. A few weeks ago I started actually using containers in earnest for the first time. Earlier versions of TrueNAS had Kubernetes and later versions are Docker-based, and while I goofed around with a few containerized apps on TrueNAS before, I always found them clunky and painful to upgrade (which is why I bought a Raspberry Pi and put Pi-hole on it rather than reconfigure the container every time there was an upgrade that failed).

With Docker it’s a bit more comfortable because I’m a happy Podman user and it all just feels more stable. And when I moved Gitea around and was thinking about whether it was worth containerizing a few things I was running in FreeBSD jails on the old TrueNAS box, I realized that I can run a container registry with Gitea and the possibilities all of a sudden presented themselves. So this post digs into how to do it and then I’ll finish up with a description of a few things that I’ve containerized (which, in the weeks since I initially set this up, has actually been quite a lot).

I’m making quite a few assumptions here and writing about my own experience, which means that I’m running the latest version of TrueNAS Scale (Electric Eel), and I’m running Gitea locally as well (as a VM on TrueNAS but it doesn’t need to be). On the MacOS desktop where I’m building the container I’m using Podman rather than Docker.

Gitea container registry with Podman

To begin with, we’ll need a Personal Access Token (PAT) in Gitea so we can read and write to the container registry. You don’t have to do anything special to enable it. This can be done by going to Gitea, then Settings -> Applications then under Manage Access Tokens you need to Generate New Token. Create this new token with write:package and read:package permisions. Copy the token once it’s created. We’ll use it for both pushing to the registry and pulling from it.

We need to login to Gitea using Podman with our Gitea username and PAT:

podman login https://git.mydomain.com

For the image you want to publish to the Gitea container registry, we’ll need to tag the image with <gitea-domain>/<username>/<image-name>:<tag>. Assuming my application is called “my-app”:

podman tag my-app git.mydomain.com/myusername/my-app:latest

Now we have to push the image to the registry:

podman push git.mydomain.com/myusername/my-app:latest

Podman should tell us this was successful if it worked, or complain if it didn’t. Now we can go to Gitea’s web interface, click on the profile icon, then Profile and then click on the Packages tab. You should see the container you just pushed.

For the most part, this should just work however I did run into one issue. Podman complained of: Error: writing blob: uploading layer chunked: StatusCode: 413, "<html>\r\n<head><title>413 Request Entity Too Large<...". This error was because Nginx, my reverse proxy, was configured to only allow 100M upload sizes. By changing this to unlimited, the push was successful. This was done by changing nginx.conf and setting client_max_body_size to 0:

http {
    ...
    client_max_body_size 0;
    ...
}

TrueNAS custom container application

In TrueNAS, a Docker registry must be signed into (which, in this case, is the Gitea registry). In Apps, click on Configuration then Sign-in to a Docker registry. Click Add Registry and set the URI to “Other Registry”. Set the following configuration in the dialog that will now be showing:

  • URI: https://git.mydomain.com
  • Name: git.mydomain.com
  • Username: your username
  • Password: the PAT for your user

Head back to Apps and then click Discover Apps then click Custom App.

The following settings will need to be set (using what we used above), minimally:

  • Application Name: my-app
  • Image Repository: git.mydomain.com/myusername/my-app
  • Tag: latest
  • Host Network: true

You’ll need to setup the Ports configuration which will allow you to expose a different port than what the container is listening to. This is really useful if you’re using a bunch of Flask-based apps, for example, that want to listen on port 5000 by default, as you can map it to a different port without changing the code. So for example:

  • Port Bind Mode: Publish port on the host for external access
  • Host Port: 32667 (the port exposed for external access)
  • Container Port: 32667 (the port the container is listening to)
  • Protocol: TCP (unless you need UDP or UDP+TCP for some reason)

Be sure to add a Portal pointing to whatever port the thing you’re running in the container is listening to. As in the example above, one of my applications is listening to port 32667 in the container:

  • Name: Web UI
  • Protocol: HTTP
  • Use Node IP: true (or checked)
  • Port: 32667

If you require storage, you’ll want to configure it here as well. There are lots of options: you can create a new ixVolume dataset to keep the data isolated. You can choose a Host Path which will basically map a directory from the host TrueNAS filesystem into the container. You can also mount remote NFS or Samba shares as well. Note to self: is a containerized Gitea and/or Plex in my future? 🤔

In my case, I did have data for the application, a simple SQLite database, and it needed to persist across container rebuilds. In my home directory on TrueNAS, I created a subdirectory called ~/apps/ and another subdirectory for this specific application, so ~/apps/my-app which needed to be mounted inside the container. This also makes backups as easy as a simple weekly tar cvzf backup-YYYYMMMDD.tar.gz apps/ to backup all the data for the custom applications.

This is done in the Storage section, such as:

  • Type: Host Path (Path that already exists on the system)
  • Read Only: true or false depending on your needs
  • Mount Path: /app/instance
  • Enable ACL: again, depends on the security you’re looking for, I left this off
  • Host Path: /mnt/storage/myusername/apps/my-app

This can be a specific file (like a configuration file) or a directory, it doesn’t really matter.

Depending on what you’re installing you may want to configure other settings here, so custommize to suit; the TrueNAS documentation is generally helpful. Click Install and it should install.

Building containers for other architectures

I ran into my first snag as I use MacOS, so my containers are built with the same architecture (ARM) but TrueNAS is running on an Intel x86_64 system. Thankfully, Podman makes this really easy and we can build an amd64 container using:

podman build --platform linux/amd64 -t git.mydomain.com/myusername/my-app:latest .
podman push git.mydomain.com/myusername/my-app:latest

Now the container image is built for the right architecture, pushed to the Gitea registry, and can be deployed.

At this point the application should run properly. Podman can be used locally to test, build, debug, and push to the Gitea container registry which will be used to pull the application for use in TrueNAS, basically in “production” (or around here, as in my wife and I can access it from any device on the network).

Every time code changes are made, necessitating a container rebuild, the podman commands above are all that need to be run to rebuild and push the container to the registry. If we want to pull the container every time we start the container, such that simply restarting the container will get the latest version, we just need to change the Pull Policy in the container configuration page. It needs to change from the default “Pull the image if it is not already present on the host” to “Always pull an image even if it is present on the host“. This is a great option, particularly if something is in active development, as it just takes a restart to pull the updated container. For something more stable, you probably want to change this and consider something like Watchtower instead, but I haven’t found the need for this yet.

So what got containerized?

For those of you who have been using containers for years this might sound like pretty simple stuff. For me, this has opened up an entire world of possibilities (I’m an old school VM guy, not a container guy like the cool kids). Except maybe now… maybe I might be one of the cool kids now.

As I noted earlier, I had a few applications running in FreeBSD jails which are pretty lightweight and easy to use. I’m finding that the containers are just as easy to use, perhaps more so, once you understand how to set them up (which is the thing I learned to do recently, as before I was just using Podman on my desktop, rather randomly).

Writing this, I’m on the second day of Christmas holidays. Before the holidays, I had containerized a few things already that had previously been running in jails:

  • A weather application that’s been running for a few years taking hourly samples of local weather data and charting it so I can look at historical information
  • My VEX server demo which pulls the Red Hat VEX for a given CVE and also talks to CVE.org/NVD/EPSS/CISA KEV/VulnCheck KEV APIs to display the information in a nice web interface

I also used the Nginx Proxy Manager in a container to configure custom domains, with Let’s Encrypt SSL certificates, to serve these up at proper internal domain names rather than connecting to my apps via their strange ports, which are always painful to remember.

Since these initial applications moved over, and over the last four days, I’ve also created two new apps that I’ve containerized:

  • A stock portfolio tracker with the ability to import data from my brokerage (so I no longer need to track this by hand)
  • A budget application with the ability to import Quicken QFX files and display whether I’m spending within my defined budget (so I also no longer need to do this by hand)

The budget application took a few hours with Cursor this morning. It now does everything my very convoluted Google Spreadsheet did (and more).

The stock portfolio tracker took a few days due to the complexity of what I was asking for: dealing with currency conversions, historical currencies, running tallies of stock purchases and sales, stocks converting to something else, stock splits and so on, plus visualizing realized gains/losses, highlighting unrealized gains/losses, showing dividends received, and more — basically showing me where I’m making decent returns and where I chose poorly. I would spend a few hours a month inputing data into a spreadsheet to track this and now it’s downloading an activity report from my brokerage that I upload. Yes, I know there are already tools that do this. Yes, I’m cheap and don’t want to buy them.

The results of AI-powered coding is shockingly good. The stock portfolio tracker was absolutely amazing and is 99% code generated just by prompting Cursor. The amount of complexity here is astounding (as is the unfortunate amount of javascript that I couldn’t code in months even if I wanted to). Interestingly, it chose two different approaches for each project. The stock app is a single HTML page with a ton of javascript and APIs. The budget app is multiple HTML pages with minimal javascript. Both use Python and the Flask framework.

Additionally, just getting all of this setup, from the Gitea container registry on, was done with the help of Gemini because this was absolutely new territory. It would have taken me three to five times longer if I had to do it on my own and figure out all the pieces. That’s not to say that I couldn’t have, but my time is precious and until I actually knew what this could all do, I didn’t know that I would want it. Getting started has always been daunting (and I’ve poked at containers a number of times in the past, but the time investment exceeded what little time I had available, until holidays of course!).

So while this was all very slick and now I have a half dozen applications running, containerized, with an easy process to update and deploy, it wouldn’t have happened if it weren’t for AI. I don’t think AI will do “all the things” or solve world hunger but dang it, it helped me learn something new, create some wickedly cool personal applications, and those same applications will save me tons of time in the future. All in all, I’d say it’s a winner and I’m pretty excited to see where it goes.

Last item on the journey is now is upgrading TrueNAS Core to TrueNAS Scale and maybe I’ll be done… for now, anyways. Whole new worlds have opened up! Who knows what will happen over the next 8 days before Christmas?

Share on: TwitterLinkedIn


Related Posts


Published

Category

Linux

Tags

Stay in touch