A secure service that manages Transmission Docker containers declaratively via gRPC over Unix sockets.
Pull the pre-built image and run:
docker run -d \
--name transctrl \
-v /var/run/docker.sock:/var/run/docker.sock \
-v transctrl-socket:/var/run/transctrl \
-v /mnt:/mnt:ro \
-e ALLOWED_MOUNT_BASE=/mnt \
ghcr.io/redsudo/transctrl:latestYour core service can then connect via the Unix socket at /var/run/transctrl/transctrl.sock.
For a complete setup, see examples/docker-compose.yml.
| Env Var | Default | Description |
|---|---|---|
SOCKET_PATH |
/var/run/transctrl/transctrl.sock |
Path to Unix socket |
DOCKER_HOST |
unix:///var/run/docker.sock |
Docker daemon address |
ALLOWED_MOUNT_BASE |
/mnt |
Only allow mounts under this path |
RATE_LIMIT_REQUESTS |
10 |
Max reconciles per window |
RATE_LIMIT_WINDOW |
60 |
Rate limit window in seconds |
from client.transctrl_client import TransmissionControllerClient
client = TransmissionControllerClient('/var/run/transctrl/transctrl.sock')
# Reconcile desired state
result = client.reconcile([
{
'id': 'user-1',
'config_path': '/mnt/configs/user-1',
'data_path': '/mnt/data/user-1',
'watch_path': '/mnt/watch/user-1',
'web_port': 9091,
'data_port': 51413
}
])
# Get current status
status = client.get_status()Security Note: Mounting the Docker socket directly gives transctrl full control over the Docker daemon. For production deployments, use a Docker socket proxy to restrict API access to only the operations transctrl needs (container create/delete). This limits the blast radius if transctrl is compromised.
See examples/docker-compose.proxy.yml for a complete example.
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: 1
POST: 1
DELETE: 1
transctrl:
image: ghcr.io/redsudo/transctrl:latest
environment:
DOCKER_HOST: tcp://docker-socket-proxy:2375
ALLOWED_MOUNT_BASE: /mnt
volumes:
- transctrl-socket:/var/run/transctrl
- /mnt:/mnt:ro
# No docker.sock mount needed
volumes:
transctrl-socket:services:
transctrl:
image: ghcr.io/redsudo/transctrl:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- transctrl-socket:/var/run/transctrl
- /mnt:/mnt:ro
environment:
ALLOWED_MOUNT_BASE: /mnt
core:
image: your-core-service
volumes:
- transctrl-socket:/var/run/transctrl:ro
environment:
TRANSCTRL_SOCKET: /var/run/transctrl/transctrl.sock
volumes:
transctrl-socket:- uv
- Docker
- Make
# Install dependencies
uv sync
# Generate gRPC code
make proto# Unit tests
make test
# Integration tests (runs in Docker-in-Docker)
make test-integrationmake build-image- Stateless: The system relies on Docker labels (
transctrl.managed=true) as the source of truth. - Isolation: Minimal capabilities (CHOWN, SETGID, SETUID) and
no-new-privilegesfor containers. - Socket Communication: Uses gRPC over Unix sockets for local, secure inter-process communication.
transctrl validates that all paths (config_path, data_path, watch_path) in reconcile requests start with ALLOWED_MOUNT_BASE. This prevents a compromised core service from creating Transmission containers with arbitrary host mounts like /etc or /root/.ssh.
Why mount /mnt:/mnt:ro?
transctrl needs to verify that requested paths actually exist before creating containers (os.path.exists()). Without this mount, transctrl can't see host paths from inside its container. The :ro (read-only) mount is sufficient—transctrl only needs to check existence, not write to these paths. The actual read-write mounts are configured via the Docker API when transctrl creates Transmission containers.
Example: If ALLOWED_MOUNT_BASE=/mnt, a request for config_path: /etc/passwd will be rejected, but config_path: /mnt/user1/config will be allowed (if the path exists).