I have a problem. Actually, it's not really a problem, it's a homelab. Multiple servers, a stack of Docker containers running everything from self-hosted services to validator nodes. I built it because I wanted control over my own infrastructure. What I didn't anticipate was how quickly "control" would turn into "constant babysitting."
The containers don't care that it's 11pm. They don't care that I'm on the couch. When something goes sideways, something goes sideways.
And every single time, the routine was the same. Connect to VPN. Open a terminal app. SSH into the right server. Then try to type an actual command on a phone keyboard and I don't mean a long command, I mean anything. Even docker ps is annoying to type on glass. You fat-finger a letter, the autocorrect kicks in, and now you're debugging your SSH command instead of your container.
I did this enough times that I started dreading it. Not the problem itself the getting there.
Looking for a Better Way
I figured someone had already solved this. There are great tools for managing Docker Portainer, Arcane, others. I use them. But they're built for deploying and maintaining stacks from a browser, not for quickly checking on things from your phone. Opening a browser, logging in, navigating to the right host that's its own kind of friction when all you want to know is whether a container is still running.
I looked for an app that could fix this. Found a few, but they were either abandoned, needed a cloud account, had a bad user experience, or wanted you to punch holes in your firewall and expose your server directly to the internet. None of that felt right. I wanted something that lived entirely on my own infrastructure auth handled properly, nothing reachable unless I chose to make it reachable.
So I built DockerTab. Dockertab
How It Works
A lightweight agent runs on your server as a Docker container. The iPhone app talks to it directly over your local network. That's the whole idea, your phone, your server, your network. Nothing else involved.
The agent is written in Go. It sits in front of your Docker socket and exposes a narrow REST and WebSocket API. Getting connected is a QR code scan start the agent, it prints a QR code in the logs, you scan it from the app. Behind the scenes it's exchanging a one time API key for a JWT that lives in your iOS Keychain. After that you're in. No usernames, no passwords, no fiddling with config on your phone.
The Streaming Parts
The thing I wanted most was to actually see what was happening not a static snapshot, live data. CPU climbing on a validator node, memory creeping up on something that's been running for weeks, logs scrolling in real time.
Both the stats and log streaming run over WebSocket. Stats push every 2 seconds. Logs stream the same way docker logs -f works in a terminal except you're reading them on your phone without having SSH'd into anything first. On the iOS side, Swift Charts handles the graphs and AsyncStream keeps the UI reactive without blocking the main thread.
Getting the WebSocket handling right took longer than I expected. Mobile connections drop, switch between WiFi and cellular, go to sleep. Making the streams reconnect gracefully and not leave the UI in a broken state was one of those problems that seems small until you're actually in it.
What About When You're Away
Most of the time, being on the same network is enough. But there are moments you're out, something feels off, you want to check where local only doesn't cut it.
I built a relay for that. It proxies the connection between your phone and your agent at home. The agent connects outbound to it, your phone connects to it, they talk through it. No inbound ports on your home network, no VPN to connect to first. Push notifications go through it too, so if a container dies while you're out, your phone knows.
The Relay and Your Privacy
The relay handles the tunneling but knows nothing about what's going through it. Container data, logs, stats, terminal output all of it passes through as opaque bytes. The relay routes based on connection IDs, never inspects the payload.
What it stores on disk: subscription state (so it knows your plan is active) and APNs/FCM device tokens for push notifications. That's it. No container names, no log content, no IP addresses, no connection history. When your phone and your agent are talking through the relay, the relay genuinely can't tell what they're saying it just keeps the pipe open.
No session records, no usage tracking, no analytics on what you're running. The architecture makes it impossible to store what it never looks at.
What's Free, What Isn't
The core of the app seeing your containers, starting, stopping, restarting them, checking logs that's free. It already beats opening a terminal on your phone.
The deeper features are behind a subscription: live log streaming, real time stats charts, remote access via the relay, push notifications, shell access, environment variables, image and volume browsers, Compose stack grouping, widgets, and Siri shortcuts. There's also a one time lifetime option if subscriptions aren't your thing.
Getting It Production Ready
This took a while to get right. A monitoring app sounds simple until you're deep in WebSocket reconnection logic, APNs edge cases, JWT rotation, and making sure nothing leaks through the auth layer. Each piece had its own rabbit hole.
The agent's API is intentionally narrow: list, inspect, start, stop, restart, stats, logs. Not because those are hard to add because they didn't belong in a monitoring app on your phone. Deciding what not to build took as much thought as what to build.
Where It Is Now
The iOS app is in App Store review right now. Android is in development, the relay infrastructure is already built for it, same architecture with Firebase Cloud Messaging instead of APNs.
I built this for myself first. If you run a homelab and you've done the VPN > terminal > SSH > fat-finger dance one too many times, I think you'll get it immediately.
If you're interested, check it out and sign up to be notified when it launches at dockertab.app.
iOS app coming soon. Android in development.
P.S. I should probably admit that Claude co-wrote this with me. Our dynamic was basically, I had the vision, Claude wrote the code, and I spent the next hour saying thatβs not quite right, until we nailed it. What would have taken me months took weeks. The big picture stuff the architecture and security was all me, but Claude was the one saving me from having to learn Swift and Go syntax from scratch at 2 in the morning.
Top comments (0)