← Field Notes
Published 8 min read

Before I Add a Service, I Write Down the Job

The admission test I use to keep useful homelab services from turning into undocumented maintenance, accidental dependencies, and permanent clutter.

In this article
  1. The job comes before the product
  2. Experiments are allowed to be experiments
  3. Someone has to depend on it
  4. A running container is not the outcome
  5. Persistence gets decided before Compose
  6. Access should be part of admission too
  7. I define recovery in plain language
  8. Every service needs an exit path
  9. The seven-question admission test
  10. What changed in my lab
  11. AI transparency

Installing a service is cheap. Owning it is not.

The easiest homelab project is adding one more service.

A container image exists. A Compose example exists. Ten minutes later the dashboard loads, the login page works, and the project feels finished.

That is usually the cheapest moment in the service’s life.

The cost shows up later: another account to secure, another update path, another configuration to protect, another dependency to remember, another thing that can fail during maintenance, and another answer to the question, “Can I remove this without breaking something else?”

I used to treat installation as the decision point. Now I treat it as the last step in a small admission process.

Before a service becomes part of the lab, I write down the job it is supposed to perform.

The job comes before the product

I try to describe the need without naming the application.

“Provide a shared household media library” is a job.

“Install Jellyfin” is an implementation.

“Collect service health and alert on meaningful failures” is a job.

“Install another monitoring platform” is an implementation.

That distinction sounds small, but it changes the entire decision.

If I start with a product, I naturally look for reasons to keep the product. If I start with the job, I can ask whether the capability already exists, whether the new service is actually better, and whether the added operational burden is worth it.

It also gives me an exit condition later. A product can feel permanent simply because I spent time configuring it. A job can be reassigned.

Experiments are allowed to be experiments

Not every service needs a business case.

A homelab is supposed to be a place where I can try things, break them, learn from them, and replace them.

The problem starts when an experiment quietly becomes infrastructure.

That transition happens faster than it seems. I test a service. Then I connect another application to it. Then someone else starts using it. Then its data matters. Six months later I cannot remove the original experiment without first reconstructing everything that grew around it.

So I label experiments mentally and operationally as temporary.

Until a service passes admission, I avoid giving it irreplaceable data, broad network access, critical integrations, or responsibilities that other people depend on. Curiosity stays cheap because I have not accidentally promoted it into production.

Someone has to depend on it

The next question is simple:

Who notices if this stops working?

If the answer is only me, the service can usually tolerate a looser maintenance window and a simpler recovery plan.

If the household uses it, expectations change. Downtime becomes visible. Client compatibility matters. Updates deserve more care. A fallback may matter. Monitoring may become useful.

If another service depends on it, the blast radius changes again.

This was one of the moments where my homelab stopped behaving like a pile of personal experiments and started behaving like infrastructure. Once other people or systems rely on something, “I can probably fix it” is not a complete operating model.

The audience does not have to turn the lab into a corporate environment. It just tells me how seriously I need to treat the service.

A running container is not the outcome

I also write down the dependencies that have to exist for the service to do its actual job.

That usually includes some combination of:

  • persistent storage
  • network access and name resolution
  • authentication or service identities
  • certificates or reverse proxying
  • databases or companion services
  • hardware devices
  • external providers
  • client applications

This is deliberately about service dependencies, not container state.

Docker Compose can control dependency startup behavior, and dependencies can be gated on a health check when service_healthy is used. But even a healthy container only proves the condition defined by that health check. It does not automatically prove the user’s end-to-end outcome. Docker’s current Compose documentation makes that distinction useful: startup order, container health, and actual service usefulness are related, but they are not the same thing. citeturn203944search0turn203944search3

A media application can be healthy while its library storage is unavailable. A dashboard can load while the upstream service it is supposed to observe is unreachable. An authentication service can be running while a client cannot complete a real login.

The admission record forces me to identify those dependencies before the first failure forces me to rediscover them.

Persistence gets decided before Compose

One of the most useful questions I ask is:

What must survive if I destroy this container?

Docker’s current storage documentation is clear about the underlying model: data in a container’s writable layer does not survive the container’s removal, while volumes are designed to persist outside the lifecycle of an individual container. Bind mounts expose host files or directories directly into a container and therefore carry their own host-path and permission considerations. citeturn203944search1turn203944search2turn203944search5

That means I want to classify the data before deployment rather than after something disappears.

I usually think in four buckets:

Configuration — the state required to reproduce how the service behaves.

Application data — databases, indexes, metadata, or other state that would materially hurt to lose.

Replaceable data — caches, generated thumbnails, temporary downloads, or anything the application can rebuild without much consequence.

Bulk data — large datasets whose protection strategy may be completely different from the application’s own configuration.

Those categories do not all deserve the same backup schedule. They do deserve a conscious answer.

If I cannot explain how I would rebuild the service after losing the host, I have not finished designing the service.

Access should be part of admission too

A new service is also a new trust decision.

Before deployment, I want to know who needs access, which networks need to reach it, whether it needs administrative privileges, whether it needs outbound access, and whether another service needs credentials for it.

That keeps convenience from becoming the default security model.

The question is not, “Can I make this reachable?”

It is, “What is the smallest amount of reachability required for the job?”

The same applies to storage. Docker notes that bind mounts are writable by default unless they are explicitly mounted read-only. That does not mean every bind mount should be read-only; many applications legitimately need writes. It does mean write access should be intentional rather than invisible. citeturn203944search1

A service that only needs to read media does not automatically need permission to rewrite the entire dataset. A monitoring tool does not automatically need administrator access everywhere simply because it is easier to configure that way.

Admission is where I want those decisions made, before “temporary” privileges become permanent.

I define recovery in plain language

I do not need a disaster-recovery binder for every container.

I do want one plain-language answer:

If this disappears tonight, what do I need to restore tomorrow?

For a small stateless utility, the answer might simply be the Compose definition and a few documented settings.

For a stateful application, it may include configuration, a database, secrets stored in the proper location, and a dependency that has to be restored first.

For something backed by bulk storage, the application may be trivial to recreate while the real recovery problem lives somewhere else entirely.

That distinction matters because “the container is backed up” is not a recovery strategy.

A useful recovery note identifies the authoritative data, the application state, the order of dependencies, and the minimum validation needed before I can call the service restored.

That thinking connects directly to the storage model I wrote about in The Storage Host Became a Contract, Not Just a Server and the deployment tradeoffs in Why Docker Became My Default, Not My Rule.

Every service needs an exit path

The most overlooked question is the one I ask at the end:

How do I remove this cleanly?

Installation leaves residue.

A service can create data, accounts, DNS records, proxy rules, firewall rules, scheduled jobs, API credentials, monitoring targets, client configurations, and dependencies in other applications.

If I only document how to launch it, removal becomes archaeology.

For anything that moves beyond experimentation, I want to know:

  • what persistent data belongs to it
  • which integrations point to it
  • what access rules were added for it
  • which credentials were created for it
  • what depends on it
  • what can be deleted when the service is retired

This has a second benefit: it makes replacement easier.

If I know what job the service performs and what systems depend on that job, I can migrate the capability instead of treating the original application as sacred.

The seven-question admission test

My actual test is intentionally small.

Before a service becomes part of the lab, I want seven answers:

  1. What job does it perform?
  2. Who or what depends on it?
  3. Which systems does it depend on?
  4. What data must persist?
  5. How is access limited?
  6. How would I recover it?
  7. How would I remove it cleanly?

None of those questions require a huge document.

For a simple service, the answers may fit in a few lines beside the deployment configuration. For something important, they may grow into actual documentation, monitoring, backups, and testing.

The point is not bureaucracy. The point is forcing the hidden operational cost into view while the service is still optional.

What changed in my lab

This rule has made me install fewer things, which I consider a success.

I still experiment constantly. I just do not confuse experimentation with adoption anymore.

Some tools prove their value and earn a permanent place. Others solve a problem I already solved elsewhere. Some are fun for a weekend and then disappear without leaving mystery accounts and abandoned data behind.

The services themselves were never the main source of complexity.

Unowned dependencies were. Undocumented state was. Recovery decisions I had postponed were.

That is the lesson I wish I had applied much earlier while building out the lab described in The Definitive Guide to Building a Homelab From Scratch.

A homelab should leave room for curiosity. Writing down the job does not make it less fun.

It just makes it obvious when curiosity has become infrastructure.

AI transparency

AI assisted with organization, editing, and verification of current Docker behavior against official documentation. The service-admission model and examples are based on my own homelab operating experience. No employer, client, credential, internal hostname, private address, or environment-specific infrastructure detail is reproduced.

JO

Written by

Jessie Owens

I run Eldritch IT and write about the systems, repairs, infrastructure decisions, and business lessons behind the work.