Self-hosted service
vmorganp/Lazytainer avatar
vmorganp/Lazytainer

Lazytainer: stopping idle containers by watching packet counts on proxied ports

Docker container lazy loading

762 stars27 forksGoMIT

At a glance

What is it?
Lazytainer is a Go service that mounts the Docker socket read-only, counts packets arriving on ports you declare, and stops or pauses the containers in a labelled group when traffic drops. It is a fit for self-hosted stacks with a few rarely used services, and a poor fit for anything that must answer the first request.
Who is it for?
Adopt Lazytainer if you run a self-hosted stack where a handful of services sit idle for hours and you accept that the first request after a sleep can fail. Do not adopt it for anything that must respond on the first packet, for containers with no HTTP or TCP listener on a declared port, or for workloads where a stop means losing in-memory state.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 48 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The idle container problem Lazytainer targets

A self-hosted server often runs more services than it uses at any one time. A wiki, a dashboard, a photo tool, a status page. Each one holds memory and burns CPU cycles while nobody is looking at it. The usual fixes are manual (docker stop when you remember) or blunt (a cron job that kills everything on a schedule). Lazytainer takes a third route: it watches the network traffic heading for a container and decides from that whether the container needs to be running at all. The README describes the loop plainly: traffic means the container runs, no traffic means it is stopped or paused. The audience is people running Docker Compose stacks on their own hardware, which the repository topics reflect with entries like self-hosted, energy-efficiency and greenit. It is not a scheduler and it is not an orchestrator. It is a small Go process that sits next to your services and reacts to packets.

How the traffic detection actually works

Lazytainer does not inspect your application. It inspects the wire. The README states it monitors network traffic to containers over a configurable interface, eth0 by default, and that you set the interface per group with the netInterface property. Two numbers drive the decision. minPacketThreshold is the minimum count of network packets for a container to be considered on, defaulting to 30. inactiveTimeout is how many seconds of insufficient activity pass before the container is stopped, defaulting to 30. A pollRate property, also defaulting to 30 seconds, controls how often the check runs. There is a second input beyond raw packet count: connected clients. The ignoreActiveClients property, false by default, lets you base the decision on packet count alone. That default matters. With ignoreActiveClients left at false, an open connection can keep a container alive even when little data is moving, which is usually what you want for a session-based web app and usually not what you want for a service that holds idle keep-alive connections. The sleep action itself is configurable through sleepMethod, which accepts stop or pause and defaults to stop. Those are different operations with different consequences, and the README does not spell out the difference beyond the two words.

Groups, labels and the proxy requirement

Nothing happens automatically. The README is explicit that Lazytainer does not start and stop all of your containers and that you must apply a label and proxy traffic through the Lazytainer container. Containers join a group with a single label, lazytainer.group=<yourGroupName>, applied to the container that will sleep. The group itself is configured with labels on the Lazytainer container, in the form lazytainer.group.<yourGroupName>.<property>=value. A group can contain more than one container, and they sleep and wake together. The ports property is the only required one. It takes a comma-separated list and, per the README table, must be the internal port rather than the exposed port: a service listening on 8080 but published on 80 should be listed as 8080. This is the detail most likely to be got wrong on a first attempt, because the number you type into a browser is the exposed one. The README also states that the ports listed for a group must be forwarded on the Lazytainer container, which is what makes the proxy arrangement work: traffic reaches Lazytainer first, gets counted, and the group is woken if needed.

Getting a stack running: the exact commands and keys

The README gives a self-contained test path. Clone the repository, change into it, and bring the stack up with docker compose up, with a note that docker-compose is the fallback if the newer command is unavailable. That stack creates two containers reachable through a third Lazytainer container. The README then walks through the expected sequence: open http://localhost:81, close the tab, wait for the logs to say stopped container, return to the same URL and see a dead page, then hit it several times to generate enough traffic to wake it. Cleanup is docker-compose down. For a real deployment, three pieces of configuration are non-negotiable. The volume mount /var/run/docker.sock:/var/run/docker.sock:ro, which the README marks as required. The port forwarding on the Lazytainer container, matching whatever you put in each group's ports label. And the group labels themselves. Two optional environment variables control logging and address families: VERBOSE=true for more detailed logs, and IPV4_DISABLED or IPV6_DISABLED to turn off either stack. The test walkthrough is worth following before you touch a production compose file, because it exercises the wake path rather than just the sleep path.

The first request problem, and other limits

The test instructions contain the honest failure mode. After the container stops, the README says navigating to the URL again should give a dead page, and only after several visits does it start. That is the design, not a bug, but it means a stopped group cannot serve the request that wakes it. Anything a user expects to answer immediately, or that a monitoring system polls on a schedule, will see failures. A health check hitting a sleeping group will either report the service down or, depending on how often it runs, keep waking it and defeat the point. The Docker socket mount is a second constraint. It is read-only, which limits what the process can do, but it still exposes the full Docker API surface to the container, and the README treats it as mandatory rather than optional. Third, this is a traffic heuristic, not an understanding of your application. A container doing background work with no inbound packets will be stopped while it is busy, because nothing in the material suggests Lazytainer distinguishes an idle worker from a working one. Groups are the only unit of control, so a service whose traffic pattern differs from its neighbours cannot be tuned separately.

How this differs from a wake-on-request proxy

The closest alternative in spirit is a reverse proxy that starts a backend when a request arrives, the way some self-hosted dashboards and home-lab proxies do. The difference is what triggers the wake. A request-triggered proxy holds the connection, starts the container, and forwards the request once the backend is listening, so the user sees a slow page rather than an error. Lazytainer does not do that. It counts packets on a declared port and reacts on its own poll interval, which defaults to 30 seconds, so the wake is not synchronised with the request that caused it. The trade-off is scope. Lazytainer is protocol-agnostic. It works on packet counts for any TCP service on the listed ports, whether that is HTTP, SSH or a game server, and it does not need to parse requests to do it. A request-aware proxy understands HTTP and can wake precisely, but only for HTTP. If your services are all HTTP and you want the first request to succeed, the proxy approach fits better. If you have a mix of protocols and can tolerate a retry, Lazytainer's counting approach covers more ground with less configuration.

Maintenance, release cadence and licence

The release history is modest and irregular. v2.0.30 landed in March 2025, v2.0.29 in February 2025, and v2.0.28 in September 2024, with the repository still receiving pushes in mid-2026. That pattern suggests a small project maintained in bursts rather than a team with a schedule. The practical implication is that a bug in the traffic heuristic, or a change in how Docker exposes container state, could sit unfixed for months. The licence is MIT, which is permissive and imposes no source-disclosure obligation on your own configuration or stack. What MIT does not do is grant you any warranty, and the mandatory read-only Docker socket mount is the thing to weigh against your own security posture rather than a licensing question. The Go implementation means the runtime artefact is a single binary inside a container, so there is no language runtime to patch separately. Upgrades are a matter of pulling a new image tag and restarting the Lazytainer container, though the README does not document a migration path between major versions, and the jump from v1 to v2 is not described in the supplied material.

Editorial conclusion

Adopt Lazytainer if you run a self-hosted stack where a handful of services sit idle for hours and you accept that the first request after a sleep can fail. Do not adopt it for anything that must respond on the first packet, for containers with no HTTP or TCP listener on a declared port, or for workloads where a stop means losing in-memory state. Before deploying, verify two things in your own compose file: that the internal port in lazytainer.group.<name>.ports matches the port the service actually listens on rather than the published one, and that every port you list is also forwarded on the lazytainer container, since the README states each group is required to have ports specified and that those ports must be forwarded on lazytainer itself. The project is MIT licensed, so the practical constraint is not the licence but the read-only Docker socket mount the README marks as mandatory.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. vmorganp/Lazytainer on GitHub
Community notes

Community notes