Portainer Agent: managing a second Docker host from one console
Table of contents
- Key takeaways
- Why an agent and not the socket
- Deploying the agent on the remote host
- What the agent actually exposes
- The shared secret
- Connecting the environment from the console
- What to check when the environment will not connect
- When this is not what you need
- Frequently asked questions
- Conclusion
- Sources
The Portainer Agent is a container you deploy on each remote machine, exposing port 9001 over TLS. The Portainer server connects to it and manages that host as one more environment, without exposing the Docker socket to the network or maintaining permanent SSH tunnels.
You have Portainer running and a second Docker machine you also want to see from the same console. The answer is not to expose the Docker socket to the network: it is to deploy an agent on the remote machine. This article covers what that agent does, how to deploy it, and what to check when the environment refuses to connect.
Key takeaways
- The agent is a container running on the remote host, listening on port 9001; the Portainer server connects out to it.
- Communication always runs over TLS, with a certificate the agent generates itself at startup.
- An unsigned request gets a 401 with the message
Missing request signature headers: the agent is not an open API. - If your server runs with its own secret, you must pass it to the agent via the
AGENT_SECRETvariable. - The alternative of exposing the Docker socket over TCP is equivalent to handing out root access to that machine.
Why an agent and not the socket
The apparently simple way to manage a remote host is to publish the Docker daemon over TCP and point Portainer at it. It is also the way to turn that machine into a liability. Docker’s own documentation is blunt about daemon access keys: "That means anyone with the keys can give any instructions to your Docker daemon, giving them root access to the machine hosting the daemon. Guard these keys as you would a root password!". Anyone who can talk to the daemon can launch a privileged container that mounts the host disk, so socket access is root access.
The agent avoids that exposure. It talks to the local socket inside the machine and publishes a far smaller surface outward: a single port, over TLS, that only serves requests signed by the server governing it.
Deploying the agent on the remote host
On a standalone Docker machine, deployment is one container:
docker run -d \
--name portainer_agent \
--restart=always \
-p 9001:9001 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
portainer/agent:latest
The two mounts do different jobs. The socket mount is what lets the agent talk to Docker. The volumes mount is what lets you browse their contents from the console; if your volumes do not live at the standard path, adjust that second mount to the real one.
If you would rather declare it alongside the rest of your infrastructure:
services:
agent:
image: portainer/agent:latest
container_name: portainer_agent
restart: always
ports:
- "9001:9001"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
On startup, the container log confirms the effective configuration. In the version tested for this article, the relevant line reports api_version=2.39.6, server_addr=0.0.0.0, server_port=9001 and use_tls=true.
What the agent actually exposes
Worth checking rather than assuming. Four calls against a freshly started agent make the surface clear:
| Call | Result |
|---|---|
http://host:9001/ping (plaintext) |
400: the agent does not serve unencrypted HTTP |
https://host:9001/ping without validating the certificate |
204 |
https://host:9001/ping validating the certificate |
fails: the certificate is self-signed |
https://host:9001/endpoints unsigned |
401 Missing request signature headers |
The agent generates the certificate at startup, valid for exactly one year from that moment. That is why validating the chain from outside does not work, and why there is no point putting that port behind a proxy expecting a certificate from a public authority: trust here is established by the Portainer server, not by the browser certificate system.
The last row is the one that matters for security. Even if someone reaches port 9001, without the signature headers the server issues they get nothing.
The shared secret
If your Portainer server starts with its own secret, the agent has to know it. It is passed as an environment variable:
docker run -d --name portainer_agent --restart=always \
-p 9001:9001 \
-e AGENT_SECRET=your-secret \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
portainer/agent:latest
It is a shared secret, so treat it as one: do not leave it in your shell history or in an unencrypted compose file inside a repository.
Connecting the environment from the console
With the agent running, you add a new environment of the agent type on the server and give it the address, DNS name or IP, along with the port. The default, and the one matching the deployment above, is 9001.
If you also need to manage the host filesystem from the console, the agent supports an extra mount of the root (-v /:/host). It is a powerful capability, which is exactly why you should add it only if you are going to use it.
What to check when the environment will not connect
In order of likelihood:
- The port is not reachable. From the server, check that port 9001 on the remote host responds. A firewall or security group in between is the most common cause.
- You are testing with HTTP. A 400 on a plaintext call is expected behaviour, not a fault. Try HTTPS.
- The certificate does not validate. Also expected: it is self-signed. It is not why the environment is failing.
- The secret is missing. If the server has
AGENT_SECRETand the agent does not, the connection is rejected however perfect the network is. - Volumes do not show up. The agent started without the volumes mount, or your volumes are not at the standard path.
When this is not what you need
If what you are dealing with is a cluster rather than two or three loose machines, the agent still works, but the conversation moves: it becomes an orchestration decision. There it pays to read first whether Docker Swarm still makes sense and, if you go that way, how to run Traefik on Swarm with certificates.
Frequently asked questions
Do I need an agent on the machine where Portainer runs? No. The server manages its own host through the local socket; the agent is for remote ones.
Can I put the agent behind a reverse proxy with a public certificate? There is no point. The Portainer server validates the agent by its signature, not by the certificate chain, and the agent already encrypts on its own.
Does the agent update containers on the remote host? Not by itself. It is the channel the server operates through; you decide the actions from the console.
Conclusion
Adding a second host to Portainer is deploying a container, opening a port and registering the environment. What matters is not the procedure, which is three steps, but why it exists: publishing the Docker socket over the network gives away root access to that machine, and the agent shrinks that surface to one encrypted port that only obeys signed requests. The Spanish version of this article is at Portainer Agent: gestionar un segundo host Docker.