Deploy Portainer stack inside the Docker Swarm cluster.

Initial information

Docker version.

$ docker version
Client:
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.13.8
 Git commit:        afacb8b7f0
 Built:             Tue Jun 23 22:26:12 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Server:
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.8
  Git commit:       afacb8b7f0
  Built:            Thu Jun 18 08:26:54 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.3-0ubuntu2
  GitCommit:
 runc:
  Version:          spec: 1.0.1-dev
  GitCommit:
 docker-init:
  Version:          0.18.0
  GitCommit:

Cluster details.

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
wholwgyqszoxv644ryv5xxuyf     swarm-cerberus      Ready               Active                                  19.03.8
medtf6p57iirdou5evwt6rmwr *   swarm-hydra         Ready               Active              Leader              19.03.8
td230ieplzm6z1uhszbrom8vr     swarm-kraken        Ready               Active                                  19.03.8

Portainer stack configuration

Define environment variables using portainer-stack.env configuration file.

$ cat portainer-stack.env
AGENT_TOKEN=$(openssl rand -base64 16)

Define stack using portainer-stack.yml YAML file.

$ cat portainer-stack.yml
version: '3.4'
services:
  agent:
    image: portainer/agent
    env_file: portainer-stack.env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - portainer_agent
    deploy:
      mode: global
      placement:
        constraints:
          - "node.platform.os == linux"
  manager:
    image: portainer/portainer
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    env_file: portainer-stack.env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    ports:
      - target: 9000          # for user interface
        published: 9000
        protocol: tcp
        mode: host
        # - target: 8000      # for EDGE agent
        #   published: 8000
        #   protocol: tcp
        #   mode: host
    networks:
      - portainer_agent
    deploy:
      placement:
        constraints:
          - "node.platform.os == linux"
          - "node.role == manager"
          - "node.hostname == swarm-hydra"
volumes:
  portainer_data:
networks:
  portainer_agent:
    driver: overlay
    attachable: true

Change swarm-hydra manager node name to reflect your configuration.

Play with the stack

Deploy Portainer stack.

$ docker stack deploy --compose-file=portainer-stack.yml container_management
Creating network container_management_portainer_agent
Creating service container_management_agent
Creating service container_management_manager

List stacks.

$ docker stack ls
NAME                   SERVICES            ORCHESTRATOR
container_management   2                   Swarm

Display stack services.

$ docker stack services container_management
ID                  NAME                           MODE                REPLICAS            IMAGE                        PORTS
5rw7smlcgo9j        container_management_manager   replicated          1/1                 portainer/portainer:latest
q7g6w3x2cujb        container_management_agent     global              3/3                 portainer/agent:latest

Display stack tasks.

$ docker stack ps container_management
ID                  NAME                                                   IMAGE                        NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
wn2tihvxlhj5        container_management_agent.wholwgyqszoxv644ryv5xxuyf   portainer/agent:latest       swarm-cerberus      Running             Running 3 minutes ago
963x6kpq39ma        container_management_agent.td230ieplzm6z1uhszbrom8vr   portainer/agent:latest       swarm-kraken        Running             Running 3 minutes ago
icg69pmacnn1        container_management_agent.medtf6p57iirdou5evwt6rmwr   portainer/agent:latest       swarm-hydra         Running             Running 3 minutes ago
slnadoef7jn0        container_management_manager.1                         portainer/portainer:latest   swarm-hydra         Running             Running 3 minutes ago                       *:9000->9000/tcp

Remove stack.

$ docker stack rm container_management
Removing service container_management_agent
Removing service container_management_manager
Removing network container_management_portainer_agent

Remove volume used by this stack.

$ docker volume rm container_management_portainer_data
container_management_portainer_data

Reverse proxy

Basic HAProxy configuration.

[...]
frontend web-frontend
  bind 1.2.3.4:443 ssl crt /etc/haproxy/ssl/cloudflare.pem
  mode http
  acl is-swarm-manager  hdr(host) -i portainer.example.org
  default_backend no-match
[...]
backend backend-local-portainer
  server swarm-manager 172.16.0.10:9000 check
[...]
backend no-match
  http-request deny deny_status 400

Read How can I configure ngnix/traefik reverse proxy to serve Portainer? for more information.

Issues

I have experienced two issues. The first one was described in Consoles don’t work over nginx reverse proxy and produce 401 #3538 bug report as access to container console does not work over a reverse proxy. The second one is related to authentication, as the internal authentication mechanism does not work with the basic auth requirement added by HAProxy or Nginx.

Portainer screenshots

Do not just look at these screenshots, but play with Docker swarm to experience these things by yourself.

Portainer dashboard

Swarm cluster
Docker stack