blog
A Docker GUI for a Linux VPS without exposing the Docker API
Published
Wanting a graphical view of your containers and refusing to open the daemon to the network are compatible. The constraint only decides which shapes of tool can work — and each shape leaves something different running on the server.
Start from the constraint
The rule is simple: the Docker daemon stays on its local UNIX socket, /var/run/docker.sock, and no TCP port is opened for it. Access to that socket is root on the host, so only processes already on the machine should reach it.
Any GUI that respects the rule needs two things: something on the server side that talks to the socket locally, and an authenticated, encrypted way for you to reach that something.
Why a browser cannot do it alone
A web page cannot open an SSH connection or a UNIX socket on a remote machine; it can only speak HTTP to an address. So a browser-based GUI always needs a web server that does the Docker part for it — either on the Docker host itself, or somewhere that reaches the host through an agent.
SSH already solves the second half of the problem. You have a key, the server checks it, the channel is encrypted, and nothing new has to listen on a public port.
Shape 1: a web panel reached only through an SSH tunnel
You can run a web panel on the server and keep it off the internet by publishing its port on the loopback address only. Your browser then reaches it through an SSH port forward.
A fresh Portainer instance expects its admin user to be created within a few minutes and locks the setup screen if nobody does, so open the tunnel first and restart the container if you miss the window.
Portainer bound to localhost on the server, reached through a tunnel
# on the server docker volume create portainer_data docker run -d --name portainer --restart=always \ -p 127.0.0.1:9443:9443 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:lts # on your computer, then open https://localhost:9443 ssh -N -L 9443:127.0.0.1:9443 deploy@203.0.113.10
The 127.0.0.1: prefix matters more than it looks. When Docker publishes a port on all interfaces, the traffic is routed by rules Docker writes itself, before a host firewall such as ufw gets to see it.
Shape 2: an agent on the server
Some tools install a small agent next to the Docker daemon. The agent talks to the socket locally and either accepts connections from a central panel or dials out to one. You get one interface for many servers, reachable from any browser.
The agent is the part that holds the socket, so its authentication and its update schedule become part of your server’s security. For a team that is a reasonable trade; it is worth knowing that you made it.
Shape 3: a desktop client over SSH
A desktop application can open the SSH connection itself and run docker commands on the server, exactly as you would in a terminal. The daemon stays on its socket, and the only thing listening on the network is the SSH server you already had.
The limits are the mirror image of the advantages: the GUI exists on the computers where you installed it, and only while it is open.
What keeps running after you close the window
- 01Tunnel to a web panel: the panel keeps running on the server with the Docker socket mounted. Only the tunnel closes.
- 02Agent: the agent keeps running and stays connected to its panel. That is the point of it.
- 03Desktop client: nothing of the client’s keeps running, unless it installed a server component for a specific job.
None of these is wrong. The real question is whether something should be awake on your server while you are not — and if so, which credentials it holds.
Where MihraOps lands
MihraOps is shape 3. It connects with the SSH key or password you already use, checks the server’s host fingerprint, and shows containers, logs and live usage without opening a port. Your server credentials stay in your operating system’s encrypted storage.
It also has a deliberately small piece of shape 2: an optional Agent you can install for deploy webhooks and for monitoring and email alerts, which have to work while your computer is off. Without it, MihraOps leaves only ordinary Docker containers on the server — your apps and, if you publish a domain, a Caddy proxy.
The limitation to accept: there is no way in from a computer that does not have the app.