Skip to content
Oday Bakkour
Back to Knowledge Hub

Dokploy Alert: A Bot Takes Over Fresh Installs Before You Sign Up

Oday Bakkour profile photo
Oday Bakkour
6 min read
Share
Dokploy Alert: A Bot Takes Over Fresh Installs Before You Sign Up

Executive Summary

  • What happens: a bot scans the internet for fresh Dokploy installs on port 3000. Before the real admin signs up, it registers the owner account itself (Dokploy issue #5156).
  • Then it uses your panel against you: it deploys a privileged compose that mounts the host's disk, adds its SSH key to /root/.ssh/authorized_keys, logs in as root over SSH and installs a crypto miner disguised as docker-daemon.service.
  • It's fast: on my Hetzner server it registered the owner 2 minutes after the installer finished. Four people have reported the same attack on four servers since August.
  • The tell: "Admin already exists" on your first visit, or an owner account you didn't create.
  • Not fixed yet: the issue is still open and labelled "enhancement". The latest releases, v0.30.9 and v0.30.10 (Oct 22), don't change first-run registration.
  • What to do: block port 3000 at your cloud firewall before you run the installer (ufw alone won't do it), sign up through an SSH tunnel, and turn off root login over SSH.

I made a short video of this on X. The panel screens in it are from my own Dokploy, with emails blurred.

A bot owns new Dokploy servers. It signs up as admin before you do.

A bot owns new Dokploy servers. It signs up as admin before you do.

What happened on my server

I installed Dokploy v0.30.8 on a fresh Ubuntu 26.04 server at Hetzner on October 20, with the official install script and port 3000 reachable from the internet. My comment on the issue has the full timeline (UTC):

  • 00:21: the installer finishes and prints http://<your-ip>:3000.
  • 00:23:07: an automated client (Go-http-client/1.1) registers the owner account. All 4 sessions in the session table came from it.
  • 00:23:09: it deploys a raw compose named orch-key-inject-<random>.
  • 00:23:58: it deploys orch-unit-install-<random>, which writes /etc/systemd/system/docker-daemon.service, tries to enable it, cleans up its own containers and prints a fingerprint of the host.

PermitRootLogin no was already set on that server, so the injected root key couldn't be used, and enabling the unit failed. I wiped Dokploy's data and reinstalled with port 3000 firewalled first.

The other three reports weren't that lucky. In each one, the attacker logged in as root over SSH a few seconds after the key appeared and started mining on pool.hashvault.pro. The first report also found a second program, /root/.orch-lab/orchestrator, scanning the internet on port 3000 for more Dokploy servers.

The race: the bot registers the owner at 00:23:07, injects its key at 00:23:09 and installs a miner at 00:23:58

The race: the bot registers the owner at 00:23:07, injects its key at 00:23:09 and installs a miner at 00:23:58

How the takeover works

Dokploy's install script publishes the panel on the host's port 3000 (--publish published=3000,target=3000,mode=host) and tells you to open http://<IP>:3000 (install.sh). Until someone creates the owner, that page lets anyone register. Whoever is first owns the panel, and the panel can deploy any compose file with full Docker access.

The bot's first compose, shortened:

orch-key-inject (shortened)
services:
  orch-key-inject:
    image: alpine:3.20
    privileged: true
    network_mode: none
    volumes:
      - /:/host:rw          # the whole host disk, writable
    # a base64 payload appends an ed25519 key (comment "dokploy") to
    # /host/root/.ssh/authorized_keys and /home/{debian,ubuntu,admin,user}/.ssh/authorized_keys

A privileged container with / mounted can change anything on the host. That's why Dokploy's Docker access is root-equivalent, and why a hijacked owner account becomes a hijacked server.

Illustration: the compose the bot deploys, shortened

Illustration: the compose the bot deploys, shortened

Check your Dokploy panel

You can spot most of it from the panel itself:

  1. Settings → Users: is the owner you? Look at Created At. An owner created minutes after install, while you weren't there, isn't yours.
  2. Settings → Sessions: sessions from an IP or device you don't know. On mine, the bot's sessions came from Go-http-client/1.1.
  3. Docker → Containers: search for orch. Leftover orch-key-inject-* or orch-unit-install-* containers (they're often stopped) are the bot's. Mine shows no results.
  4. Deployments: any compose you didn't create.

Audit logs would help here, but in Dokploy they're part of the Enterprise license.

My own panel: Settings → Users, the owner row and when it was created

My own panel: Settings → Users, the owner row and when it was created

Docker → Containers, searching for "orch": no results on my server

Docker → Containers, searching for "orch": no results on my server

Check the host

On the server, as root:

check-host.sh
# the bot's key has the comment "dokploy"
grep -l dokploy /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys

# the fake "Docker support daemon" and its files
ls -la /etc/systemd/system/docker-daemon.service /bin/docker-daemon /root/.orch-lab
systemctl status docker-daemon

# leftover containers, including stopped ones
docker ps -a --filter "name=orch-"

If any of these turn something up, treat the server as compromised. Remove the key and stop the unit so the attacker can't come back right away, then rebuild the server from scratch and rotate every secret that was on it. The reporters on the issue did the same. A host where someone had root can't be trusted after a cleanup.

Install Dokploy safely

Until Dokploy closes the gap, set up the server so nobody but you can reach port 3000 while the owner account doesn't exist yet.

1. Block port 3000 before you run the installer. Use your provider's firewall (Hetzner Cloud Firewall, AWS security groups, DigitalOcean Cloud Firewall…) and allow only SSH from your IP. ufw is not enough: Docker publishes ports through its own iptables rules, and Docker's docs say that traffic "gets diverted before it goes through the ufw firewall settings". If you only have iptables, filter in Docker's DOCKER-USER chain instead. Also check that the rule covers IPv6: the first reporter's OVH firewall didn't.

2. Sign up through an SSH tunnel. With 3000 closed to the internet, forward it to your laptop and open the panel there:

tunnel.sh
ssh -L 3000:localhost:3000 you@your-server
# then open http://localhost:3000 on your laptop and create the owner

3. Turn off root login over SSH. This is what saved my server. Create a sudo user with your key first, then:

/etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no

Reload SSH with systemctl reload ssh, and test a new login before you close the current session.

4. After setup, put the panel behind its own domain with HTTPS and keep 3000 closed. Only open it again if you need it, and only to your IP.

Before you run the installer: firewall :3000, sign up over SSH, no root over SSH

Before you run the installer: firewall :3000, sign up over SSH, no root over SSH

The fix Dokploy needs

In my comment I suggested a one-time setup token: the installer generates a random token, prints it with the URL, and the first-owner sign-up refuses any request without it. Jenkins does the same with initialAdminPassword. A bot scanning port 3000 then gets nothing. Fallbacks would be a short registration window after first boot, a localhost-only first run, and a per-organization policy that blocks privileged containers and / mounts by default, which would have stopped every stage of this attack even with the owner account taken.

Another reporter proposed creating the owner from the installer over SSH and keeping 3000 on loopback until that's done. Either closes the race. If you run Dokploy, add your vote or your evidence to issue #5156.

Sources

Add Oday Bakkour as a preferred source on Google

Comments

Share your thoughts and join the conversation

Leave a Comment

Loading comments...
RELATED