itool — local internal tools runner
itool.sh is a small CLI for running many independent internal tools on a single machine in a predictable, conflict-free way using Docker.
The design is intentionally conservative: machine-local facts live in a registry, application facts stay inside projects, and itool only glues the two together.
What’s in this repository
.
├── itool.sh # the CLI (symlink this into your PATH)
└── README.md # this document
After cloning, install it like this:
ln -s $(pwd)/itool.sh ~/.local/bin/itool
Goals
- Run many independent internal tools on one machine
- Zero port collisions
- Stable URLs per machine
- No guessing or duplication of app configuration
- Minimal, human-editable registry
- Docker-based, but not tied to Traefik, Avahi subdomains, or shared proxies
- Clear separation between machine concerns and project concerns
Core invariants
-
The registry owns machine-local facts only
- Project path
- Host port (unique per machine)
-
Projects own application facts
- Container port (
PORTin.env) - How the server is started
- Docker image and runtime config
- Container port (
-
itoolnever guesses- If the project doesn’t declare its container port, startup fails
- No “smart defaults” for app internals
-
Ports are assigned once and stay stable
- Auto-assigned at registration
- Persisted in the registry
- Reboots do not change them
File layout (outside this repo)
itool keeps all machine-local state under ~/.config/itools:
~/.config/itools/
registry.ini
Each internal tool is a normal project directory:
project/
docker-compose.yml
docker-compose.override.yml # generated, machine-local (DO NOT COMMIT)
.env
Dockerfile
Registry format (one file for all tools)
The registry lives at:
~/.config/itools/registry.ini
Each tool is a section containing only its path and host port.
Example
[taskpe]
path = /home/username/dev/python/task-progress-estimator
port = 8611
[search]
path = /home/username/dev/internal/search-service
port = 8612
That is the entire registry contract.
Port management
-
itool registerautomatically allocates a host port from a reserved range (default: 8600–8999) -
The chosen port must:
- not already be assigned in the registry
- not currently be in use on the host
-
An explicit port can be requested with
--port -
Once registered, a tool’s port does not change unless you edit the registry
Project requirements
Every project must define its container port itself.
.env
PORT=8501
This is the port the application listens on inside the container.
docker-compose.yml
The base compose file must not hardcode host ports:
services:
app:
build: .
env_file: .env
restart: unless-stopped
Machine-local wiring (generated)
itool generates a compose override per project:
docker-compose.override.yml
# Generated by itool (machine-local). DO NOT COMMIT.
services:
app:
ports:
- "8611:${PORT}"
This file is:
- machine-specific
- regenerated automatically
- the only place where host ↔ container ports are connected
Commands
itool register <name> <path> [--port N]
- Registers a project
- Auto-assigns a unique host port
- Writes the registry entry
- Generates
docker-compose.override.yml
itool edit-registry
- Opens
registry.iniin$EDITOR - Validates all entries on save
- Regenerates all overrides
itool list
- Lists registered tools with name, port, and path
itool info <name>
- Shows details for one tool
itool <name> up
- Starts the tool with Docker Compose
- Fails fast if
.envorPORTis missing - Prints and opens the tool URL
Other lifecycle helpers
itool <name> down
itool <name> restart
itool <name> logs
itool <name> ps
itool <name> build
itool <name> path
URLs and networking
Each tool is reachable at:
http://<name>.local:<host_port>
Name resolution is intentionally outside the registry:
- via
/etc/hostsautomation or wildcard DNS (e.g.dnsmasq) - no Traefik reuse
- no Avahi subdomain management
- no shared reverse proxy required
Example: taskpe (a webapp)
Project defines:
PORT=8501
Registry assigns:
port = 8611
Resulting URL:
http://taskpe.local:8611
The webapp still runs on 8501 internally; only the host port is machine-specific.
Behavior on reboot
- Docker restarts containers (
restart: unless-stopped) - Registry and overrides persist
- URLs remain stable
- No manual action required
What itool intentionally does not do
- Guess container ports
- Infer application types
- Modify
.envcontents - Share ports or proxies with unrelated systems
- Hide configuration errors
Mental model
- Registry = machine facts
- Project = application facts
- Compose override = glue
itool= invariant enforcer
If something is wrong, it fails loudly and early.
Running itool on a home-server setup
This section explains how to run itool on an always-on home server (NUC, mini-PC, NAS, or spare machine) so tools are reachable from your LAN and survive reboots without manual intervention.
Assumptions
- The machine runs Linux with systemd
- Docker and Docker Compose v2 are installed
- The machine has a stable hostname (e.g.
homeserver) - Tools are internal / trusted (no public internet exposure by default)
Networking model (recommended)
-
Each tool:
- runs in its own container
- listens on its container port (
PORTin.env) - is mapped to a unique host port (from the registry)
-
Containers bind to
0.0.0.0so they’re reachable from the LAN -
No reverse proxy required
Resulting URLs from other devices on your network:
http://homeserver:8611
http://homeserver:8612
This is the simplest, most robust setup for a home server.
Project configuration for LAN access
In each project’s .env:
PORT=8501
HOST=0.0.0.0
Ensure the app actually listens on 0.0.0.0:
- Streamlit:
--server.address=0.0.0.0 - ASGI servers:
--host 0.0.0.0
If the app only binds to 127.0.0.1, it will not be reachable from other machines.
Docker restart behavior
All tools should include in docker-compose.yml:
restart: unless-stopped
This ensures:
- Containers start automatically after a reboot
- Tools come back online without running
itool upagain
Verify Docker itself is enabled:
sudo systemctl enable docker
Firewall considerations
If a firewall is enabled (e.g. ufw), allow the itool port range:
sudo ufw allow 8600:8999/tcp
Or selectively allow only ports in use:
sudo ufw allow 8611/tcp
sudo ufw allow 8612/tcp
Name resolution on the LAN
Option 1: Use the hostname (simplest)
Most home networks already support this:
http://homeserver:8611
No additional configuration required.
Option 2: Local DNS (optional)
If you run a local DNS (router, Pi-hole, dnsmasq):
-
Create DNS entries pointing to the server IP:
taskpe.homelab → 192.168.1.10 -
Access as:
http://taskpe.homelab:8611
This is purely cosmetic and not required for itool.
Managing tools remotely
From the server itself:
itool list
itool taskpe restart
itool search logs
From another machine:
-
Use SSH for control:
ssh homeserver itool taskpe restart -
Access the UI via browser over LAN
Backups and persistence
Machine-local state to back up:
~/.config/itools/registry.ini
Project data:
- Project directories themselves
- Any Docker volumes defined by tools
Containers can always be recreated; the registry is what preserves stable ports.
Security notes
-
This setup assumes a trusted LAN
-
Tools are not authenticated by default
-
Do not forward these ports to the public internet without:
- authentication
- TLS
- and ideally a reverse proxy
If you later need remote access, put a proxy (Caddy, Nginx) in front of selected tools explicitly—itool stays unchanged.
Summary for home-server usage
- Use fixed host ports from the registry
- Bind apps to
0.0.0.0 - Rely on Docker’s restart policy for persistence
- Access tools via
hostname:port - Keep
itoolfocused on orchestration, not networking policy
This keeps the home-server setup simple, resilient, and easy to reason about.