How I Stopped Drowning in Incus Containers (and Started Loving My Homelab Again)
I'm a developer from Jakarta, Indonesia. An average one, or perhaps little bit mediocre. Likes to learn new thing. Currently learning Rust, Vlang, C++17/20, and some frontend stuffs.
A few months ago, I had a problem:
I couldn’t remember what half my containers were for. Seriously.
I’d run incus list and see:
+-----------------+---------+----------------------+-----------+-----------+
| NAME | STATE | IPV4 | TYPE | SNAPSHOTS |
+-----------------+---------+----------------------+-----------+-----------+
| web01 | RUNNING | 10.209.83.196 (eth0) | CONTAINER | 0 |
| api-dev | RUNNING | 10.209.83.177 (eth0) | CONTAINER | 0 |
| tile-cache | RUNNING | 10.209.83.201 (eth0) | CONTAINER | 0 |
| db-geo | RUNNING | 10.209.83.155 (eth0) | CONTAINER | 0 |
| temp-test | STOPPED | | CONTAINER | 0 |
| legacy-app | STOPPED | | CONTAINER | 0 |
... ... ... ...
+-----------------+---------+----------------------+-----------+-----------+
And I’d stare at it like…
- “Wait — does api-dev use db-geo? Or was that web01?”
- “Is tile-cache still needed? Who uses it?”
- “Why is there a temp-test from six weeks ago??”
My lab had become a container graveyard — full of forgotten services, scattered docs, and tribal knowledge only I (barely) remembered.
The Breaking Point
One day, I cloned a container for a new project, started it up… and got this SSH warning:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
Because — duh — it inherited the same SSH host keys as the original. And that was it. I’d had enough.
I wanted:
To know what each container was for
To not break things by accident
To spin up full environments with one command
To stop guessing
So I built LabKit.
What Is labkit?
labkit is not another orchestration tool. It’s not Kubernetes. It’s not even trying to be.
It’s a lightweight, Git-powered wrapper around Incus that helps you treat your containers like actual projects — not just random Linux boxes.
Think of it like create-react-app, but for full development environments.
With labkit, I now do this:
labkit new backend-api
cd backend-api
labkit node add postgres
labkit node add api-server
labkit requires add tile-server
labkit up
And boom — my whole stack is up, documented, and connected.
How It Works (Without the Boring Parts)
Every lab is just a folder with:
backend-api/
├── lab.yaml # config: template, deps
├── nodes/
│ ├── postgres/ # auto-created
│ │ ├── manifest.yaml # "This is a PostGIS DB"
│ │ └── README.md # Usage notes, queries, tips
│ └── api-server/
│ ├── manifest.yaml
│ └── README.md
└── shared/ # scripts, configs, certs
When I run labkit up:
It starts
postgresandapi-serverBut also checks if
tile-server(a shared map tile server) is running — and starts it if neededAll docs are mounted into the containers at
/lab/nodeso anyone can read themAnd if I ever forget what
api-serverdoes? I justcat /lab/node/README.md
No more Slack pings. No more digging through notes.
The Magic: Shared Infrastructure That Doesn’t Get Killed
Here’s my favorite part:
I have a shared OSM tile server used by three different labs.
Before labkit, if I ran incus delete --all, I’d accidentally kill it.
Now, I tag it:
incus config set tile-server user.pinned=true
incus config set tile-server user.required_by=backend-api,frontend-map,analytics-dashboard
And when I run labkit down in any lab?
It stops local containers
Leaves
tile-serveraloneEven if it’s not “in” that lab
It just knows: “Someone else needs this.”
Bonus: Start Only What You Need
Sometimes I don’t want the whole stack.
So I added:
labkit up --only api-server
Perfect for testing one service without bringing up five others.
And yes — --dry-run works everywhere:
labkit down --only postgres --dry-run
Output:
Planned actions:
Stop local node: postgres
DRY RUN: No changes applied
Peace of mind before pressing “go”.
Why This Matters
labkit didn’t just organize my containers. It changed how I work.
Now:
New projects take minutes, not hours
I never lose context
I can safely clean up old stuff because I know what depends on what
Documentation lives where it’s used — inside the environment
And yes, no more SSH host key warnings 😌
It’s not fancy. It doesn’t scale to 1000 nodes.
But for real developers managing real homelabs, it’s exactly what we need.
Want to Try It?
Check out github.com/aprksy/labkit or the project page https://labkit.aprksy.dev
Then:
labkit new myproject
labkit node add web
labkit up
That’s it.
No YAML sprawl. No complex CRDs. Just containers that make sense.
Final Thought
We spend so much time building apps that we forget to build good environments for building them.
labkit is my way of fixing that. And honestly? I finally enjoy using my homelab again.
No more chaos. Just clarity.
I guess I will write another post on how I setup my homelabs in more detailed.



