Contents

Dockge: Compose Stacks With a Sane UI

A web editor for compose files that leaves the compose files exactly where you left them

Contents

The reason I resisted container UIs for years is that most of them take custody of your configuration. You paste a compose file into a text box, the tool stores it in its own database, and from that moment the file on disk and the thing that is running have no relationship. Your git log is a museum. The truth lives in a SQLite blob you cannot grep.

Dockge is the tool that fixed that complaint, and it fixed it by having almost no opinion at all. Every stack is a directory under /opt/stacks/<name> containing a compose.yaml and a .env. Edit them in the web editor, and the file on disk changes. Edit them with vim over SSH, and the web UI shows your changes. There is no import step and no export step, because there is nothing to import from.

It comes from Louis Lam, who also wrote Uptime Kuma, and it shares that project’s sensibilities: small, focused, unfashionably practical, and slightly rough at the edges you rarely touch. If you already run Uptime Kuma as a status page, you will recognise the shape of the thing within about ten seconds.

What it is for

Advertisement

Dockge manages compose stacks. That is the whole scope. It will not build images. It has no concept of a Git repository, no templates, no app store, no blue-green deploys. What it gives you is five things:

A live editor for the compose file, with syntax highlighting and a form-based helper for people who cannot remember port syntax. A start/stop/restart/update control per stack. A streaming log view with all services interleaved, colour-coded by container. A converter that takes a docker run command and turns it into compose YAML. And an agent mode that lets one Dockge instance drive stacks on other machines.

That last one is the reason I keep it. The docker run converter is the reason everyone else keeps it.

Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
services:
  dockge:
    image: louislam/dockge:1
    restart: unless-stopped
    ports:
      - 5001:5001
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/app/data
      - /opt/stacks:/opt/stacks
    environment:
      DOCKGE_STACKS_DIR: /opt/stacks

The critical detail, and the one everybody gets wrong on the first attempt: the path in DOCKGE_STACKS_DIR must be the same string on the host and inside the container. Dockge shells out to docker compose using the socket, and the Docker daemon resolves paths in the host’s filesystem namespace. If you mount /opt/stacks at /app/stacks inside the container and tell Dockge to look there, it will read your compose files perfectly and then hand /app/stacks/media to a daemon that has never heard of it. The error message is unhelpful. Mount it at the same path both sides and the problem does not exist.

Then, on each additional host, run the identical stack and add it in the UI under Settings → Agents with its address and credentials. Dockge’s agents are full Dockge instances that happen to be driven remotely, which means if the central one dies you can still log into each machine directly. That is a nice property for a homelab.

Migrating in

Advertisement

There is no migration, which is the point. Move your existing directories:

1
2
3
4
5
6
$ sudo mkdir -p /opt/stacks
$ sudo mv ~/docker/media /opt/stacks/media
$ sudo mv ~/docker/paperless /opt/stacks/paperless
$ ls /opt/stacks/media
compose.yaml  .env
$ docker compose -f /opt/stacks/media/compose.yaml up -d

Refresh Dockge and both stacks are listed, running, with their logs available. It reads the directory. That is the entire onboarding process, and after Portainer’s import dialogs it feels like a trick.

Two constraints on naming. Dockge expects compose.yaml — it also accepts docker-compose.yml, but the newer name is what it writes, and having half your stacks on each is a small ongoing annoyance. And the directory name becomes the stack name and therefore the compose project name, so renaming a directory orphans the running containers. Rename by taking the stack down first.

The docker run converter, which everybody uses

Half the self-hosted software on the internet documents itself with a docker run command of eleven lines and no compose example. Dockge has a text box: paste the command, get YAML.

1
docker run -d --name jellyfin --user 1000:1000 --net=host   -v /srv/jellyfin/config:/config -v /srv/media:/media   --restart=unless-stopped jellyfin/jellyfin:10.10.3

becomes a compose file with the right keys in the right places, which you then edit into the shape you actually want. It handles the common flags — ports, volumes, environment, restart policy, user, network mode — and produces something you would have typed yourself.

This is a five-minute time saving that you make twenty times a year, and it is the single feature people mention when they recommend Dockge. It removes the specific friction that makes you think “I’ll just run it with docker run for now”, which is how you end up with a container nobody can account for and no file describing it.

Where it gets approximate: device mappings, --cap-add, --security-opt, cgroup parents and healthcheck flags come out inconsistently, and anything with shell quoting inside it needs a human. Read the output. It is a first draft that saves you the boring 90%.

Where it sits against the others

I compared Komodo and Portainer at length and concluded that the interesting axis is who owns the truth. Portainer owns it in its database. Komodo owns a definition of it in Git and reconciles. Dockge owns nothing — the filesystem owns it, and Dockge is a text editor with buttons.

That third position is stronger than it sounds, because it composes with everything else. My stacks directory is a Git repo. I commit from the shell, Renovate opens PRs against it, and Dockge happily edits the working tree without knowing any of that exists. When Dockge writes a change, git diff shows it. When I want a stack back the way it was on Tuesday, git checkout does it and the UI simply reflects the file.

No tool in this space is designed for that workflow. It falls out of Dockge having declined to build a data model. There is a version of this where that is laziness; in practice it is the most useful design decision in the project.

The trade is that everything the database was buying you is gone. No user permissions beyond a single login. No audit of who changed what — git blame is your audit, if you remembered to commit. No registry credential management. No overview of image versions across hosts. I fill the last gap with Diun watching the registries and sending me a message, which costs 30 MB and works better than any dashboard.

The update button, and its sharp edge

Advertisement

Each stack has an Update button. It runs docker compose pull followed by up -d, which is exactly what you would type. Convenient, and one click away from an unplanned upgrade of something you meant to leave alone. It has no notion of a maintenance window, no snapshot, no rollback beyond your own backups. I have pressed it on the wrong stack exactly once — enough to make me pause over it now.

If the stack pins real versions rather than latest, that button does very little, which is the correct amount. Pull on a pinned digest is a no-op. That behaviour is another argument for pinning tags properly: it turns a dangerous button into a harmless one.

Agent mode in practice

Four hosts, one browser tab. The stack list on the main instance shows everything across all four with the agent’s name beside each entry, and switching between them costs nothing. Logs stream from the remote host through the central instance over the same WebSocket, so a machine that is otherwise firewalled off from my desktop is still readable.

The rough edges are honest ones. An agent that goes offline — the cupboard laptop suspends itself if I forget to tell it otherwise — leaves its stacks in the list looking alive until you click one. Reconnection is automatic but unhurried. And there is no cross-host operation of any kind: you cannot move a stack from one agent to another, or run one action against all four. Each host is a separate tab in the same window.

For what it costs, that is fair. The alternative implementations of “see all four hosts at once” I have tried involved a database, an agent protocol and a fortnight of my life. This one involved adding a hostname and a password to a settings page.

Environment variables, secrets and the thing to watch

Each stack gets a .env alongside its compose file, and Dockge edits it in a second tab. This is pleasant right up to the moment you realise what it means: your database passwords are now one click away in a web UI, rendered in plain text, behind a single username and password with no second factor.

Dockge has one local account and nothing else. No OIDC, no per-user scoping, no read-only role for the person who just wants to look at logs. Whoever can reach port 5001 can read every secret on four machines and start a container that mounts the host root. Any tool holding the socket can do that, so the criticism lands on the architecture rather than on Dockge. What Dockge adds is that the .env editor makes the secrets convenient, and convenience is what turns a theoretical exposure into a habit.

My compromise: nothing sensitive lives in .env for the stacks that matter. Those read from files:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
services:
  db:
    image: postgres:16.4-alpine
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: /opt/secrets/db_password

/opt/secrets sits outside the stacks directory, is mode 0600 and root-owned, and is excluded from the Git repo. Dockge shows the compose file referencing a path and shows nothing else. The UI stays useful and stops being a password vault.

The other half is access. Dockge listens on 5001 and should never be reachable from anywhere you have not decided it should be. Mine is bound to the loopback interface and reached through a reverse proxy that enforces the same authentication as everything else, so the single Dockge password is a formality behind a real login rather than the only door. If you take one thing from this section: do not put port 5001 on a LAN you share with a smart TV and consider the job done.

Troubleshooting

Stacks show as “inactive” though the containers are up. Dockge matches by compose project name, which defaults to the directory name. Containers started with an explicit -p project name, or with name: set inside the compose file to something else, will not be associated. Either align the names or accept the stack living outside Dockge.

Editor saves, nothing happens. Check file ownership. Dockge runs as root in the container and writes as root; if you have been editing as your own user with a group-writable directory, the write can succeed while your shell edits silently conflict. Pick one ownership model for /opt/stacks and stick to it.

Agent shows offline. The agent connection is a WebSocket to port 5001 on the remote host. Any reverse proxy in front of it needs WebSocket upgrade headers passed through, which is the default in some proxies and a config line in others. Test by hitting the agent’s UI directly first; if that works and the agent link does not, it is the proxy.

Logs stop streaming after a while. Same cause. An idle-timeout on a proxy in the path kills the socket and Dockge does not always reconnect visibly. Raise the read timeout.

docker run converter produces something that will not start. It handles common flags well and exotic ones poorly — device mappings, cgroup parents and some network modes come out approximate. Treat the output as a first draft. It saves you the boring 90%.

Is it worth it?

Dockge is worth it if the sentence “I want a web UI but I refuse to give up my compose files” describes you. That was precisely my position, and I have run it for months without a complaint that mattered.

It suits a homelab in the two-to-five-host range where you are the only administrator and your stacks already live in directories you back up. It is a poor fit if you need multi-user access control, if you want image builds, or if you have thirty stacks and need to see across them — the UI lists stacks, and past about fifteen the list is a list.

The real competition for that slot is a terminal and a bit of muscle memory. I still do most of my work over SSH, because docker compose logs -f is faster to type than the UI is to load. What Dockge earns is the evening use: something has fallen over, I am on the sofa with a tablet, and I want to read the logs and restart one service without finding a laptop. For that, a fast page that shows every stack across four hosts and streams logs on tap is worth every one of its 90 MB.

Install it, point it at your existing directories, and if you dislike it, delete the container. Your stacks will not notice it has gone. That is a rare thing to be able to say about a management tool, and it is most of the recommendation.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.