Portainer For Container Management

Portainer logoUsing the nginx-proxy and Let’s Encrypt companion containers, this is the new, recommended method to install Portainer on your server with a free Let’s Encrypt SSL certificate. The benefits of this method are:

  • Creating a new Docker network ensures the containers can look up each other’s IP addresses by container name.
  • Portainer user data is persisted on the host. For updates, the container can be recreated with no data loss.
  • With this setup, multiple app containers share port 80 and port 443 on a single server, with different domains for each.
  • The nginx-proxy container automatically creates a proxy pass rule in the nginx configuration whenever a new container is started with the VIRTUAL_HOST (required) and VIRTUAL_PORT (optional, if the destination container listens on port 80) environment variables.
  • The letsencrypt-nginx-proxy-companion container automatically obtains an SSL certificate for any containers that are started with the LETSENCRYPT_HOST and LETSENCRYPT_EMAIL environment variables.
  • The containers must be initialized in the order described below.

docker network create dockernet

docker run --name nginx-proxy --net mynet -p 80:80 -p 443:443 -v ~/certs:/etc/nginx/certs -v /etc/nginx/vhost.d -v /usr/share/nginx/html -v /var/run/docker.sock:/tmp/docker.sock:ro --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy -d jwilder/nginx-proxy

docker run --name letsencrypt-nginx-proxy-companion --net mynet -v ~/certs:/etc/nginx/certs:rw -v /var/run/docker.sock:/var/run/docker.sock:ro --volumes-from nginx-proxy -d --restart always jrcs/letsencrypt-nginx-proxy-companion

docker run --name portainer --net mynet -v /var/run/docker.sock:/var/run/docker.sock -v portainer:/data -e VIRTUAL_HOST=domain.com -e VIRTUAL_PORT=9000 -e LETSENCRYPT_HOST=domain.com -e LETSENCRYPT_EMAIL=admin@domain.com --restart always -d portainer/portainer


 

Portainer is an open source management UI for a Docker Host or Swarm that puts a user friendly, web-based management console in front of Docker’s command line interface. Other options such as Shipyard and Panamax also exist, but Portainer is by far the most popular on the Docker Hub with over 62 million pulls.

Portainer Docker GUIPortainer’s own comparison table touts their product as the most feature-rich, particularly when it comes to managing Docker networking, volumes and container templates . You’ll notice that Rancher is also mentioned as a competitor, but we’d put their product in a category of its own since they’re uniquely able to manage Kubernetes, Docker Swarm as well as Apache Mesos clusters.

Like most open source companies, including Docker themselves, Portainer is free with commercial support. So why not take it for a spin on your server? In this tutorial, you’ll learn how to serve up Portainer on a Docker host, through an nginx reverse proxy with SSL/TLS.

The prerequisite is you need to have the Docker Engine installed on a Linux machine, which makes it a Docker host. On a fresh machine (as root), install Docker by running the script from get.docker.com. Note that this automatically installs the latest Edge release for your distribution.

curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh

To run any of the Docker commands beginning with docker you must be logged in as root, unless you add a regular user account to the docker user group using the command usermod -aG docker username. Log out and log in for this to take effect.

Why put an nginx reverse proxy with SSL/TLS in front of your Portainer instance? Security.

When you start a Portainer instance, you grant it full access to your Docker Host’s API through a Unix socket. Portainer grants you administrative access to your Docker Host. You certainly don’t want your Portainer admin password intercepted by sending it through plain text. That’s why it’s recommended to encrypt all of Portainer’s pages, particularly the login form with HTTPS.

You will use two Docker containers, one containing Portainer and the other one, nginx.

By default, the Portainer container has a built-in web server that binds to port 9000, which is immediately accessible from the public IP address of your server, but insecurely over plain HTTP. We are overriding this by creating a virtual network using Docker and assigning Portainer a static IP address on the 172.18.0.0/16 subnet.

The nginx container is then launched as a reverse proxy on the same subnet, handing off all traffic to the Portainer Docker container securely. For the nginx container, we will publish ports 80 (HTTP) and 443 (HTTPS), making it accessible from a web browser. To secure its traffic, this guide will use a free SSL certificate from Let’s Encrypt certificate authority.

Step-by-Step Guide to Setting Up Portainer

Before you begin, SSH into your Linux server with the Docker daemon installed (i.e. your Docker host). This tutorial will outline how to install Portainer on the same machine as the Docker host you want to administer. However, note that it is also possible to connect to a remote Docker host and manipulate it using Portainer.

Create the Docker network.

Docker’s default behavior is to assign containers private IP addresses within the IP range 172.17.0.0/16, i.e. from 172.17.0.0-172.17.255.255. For this example, create a custom Docker network with the subnet 172.18.0.0/16 using the docker network command.

docker network create --subnet=172.18.0.0/16 mynet

Pull, run and start the Portainer container.

Now it’s time to launch Portainer as a Docker container. By distributing the software through the Docker Hub, the developers have made it super easy to stay up to date with the latest version. All you need to do is pull the new image when there is an update.

docker pull portainer/portainer:latest

docker run --name myportainer -d --net mynet --ip 172.18.0.2 -p 127.0.0.1:9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --restart always portainer/portainer:latest

In this docker run command, the arguments specify the following.

  • Container name --name: myportainer
  • Run container in background -d
  • Docker network name --net: mynet
  • Private static IP address to assign --ip: 172.18.0.2
  • Ports to bind the container to -p: 127.0.0.1:9000:9000
    • The first part, 127.0.0.1 is a loopback address, meaning that the port should only be opened on the local machine. 9000:9000 means that 127.0.0.1:9000 on the Docker host should be directed to port 9000 within the container.
  • Sockets to map the container to using Docker volumes -v: /var/run/docker.sock:/var/run/docker.sock
    • The container should have access to the Docker API socket running on the host machine.
  • Restart behavior --restart: always
    • The Docker daemon will always attempt to restart the container if it unexpectedly stops, or if the system is restarted. This saves you the hassle of issuing the command, docker start [container name], to manually starting the container on each boot.
  • Docker image name: portainer/portainer:latest

Finally, start the Portainer Docker container you have created, using the docker start command.

docker start myportainer

Pull, run and start the nginx container.

Next, you will issue a similar series of commands to bring up the nginx web server. This is the instance of the server we will configure as a reverse proxy to your Portainer container, because otherwise you wouldn’t be able to access Portainer from your public IP address and/or hostname.

docker pull nginx

docker run --name mynginx -d --net mynet --ip 172.18.0.3 -p 80:80 -p 443:443 --restart always nginx

docker start mynginx

Enter the Bash shell of the nginx container.

Like any instance of nginx, this container-based version needs to be configured using text-based configuration files. To access the configuration file, default.conf, you need to use the docker exec command to enter the container’s Bash shell.

docker exec -it mynginx /bin/bash

If you’ve done this part right, you’ll notice that your command prompt changes from root@yourserver # to root@container_id #. Now you can start executing commands that will take effect not on your host machine, but rather inside the container.

Since the container runs on Debian, first run apt-get update and apt-get upgrade to ensure your packages are up to date. Also, you will need a text editor for the later steps. We suggest nano, since it is the most beginner-friendly. Run apt-get install nano to install it.

If you happen to be running an older version of Docker on your host machine, you’ll need the following command to pass the TERM environment variable, which will enable you to open a text editor inside your container. Otherwise you may encounter the message “Error opening terminal: unknown” when using certain functions of the shell. Refer to Github issue #9299 on the Docker project page for more information regarding this.

export TERM=xterm

Modify the default nginx configuration file to configure a reverse proxy.

This passes all traffic from http://example.com/portainer or http://###.###.###.###/portainer to the Portainer container. This is an insecure reverse proxy over HTTP for now, created simply as a test that everything is working before you obtain the SSL certificate.

Remember to replace domain.com with your actual public IP address or domain name.

Run nano /etc/nginx/conf.d/default.conf to open the text editor, then copy & paste the following:

upstream portainer {
server 172.18.0.2:9000;
}

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name domain.com;

root /usr/share/nginx/html;
index index.html index.htm;

location / {
try_files $uri $uri/ /index.html;
}

location /portainer/ {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://portainer/;
}

location /portainer/api/websocket/ {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_pass http://portainer/api/websocket/;
}

}

After pressing Ctrl-X and typing ‘y’ to confirm you want to exit nano and save the file, use the following commands to test your edited configuration file, and make your changes take effect inside nginx:

nginx -t, then assuming there are no syntax errors, nginx -s reload.

Visit http://example.com/portainer or http://xxx.xxx.xxx.xxx/portainer and you should see a web page prompting you to set up your Portainer admin password, but don’t do that yet! Wait until you secure the reverse proxy first with a SSL certificate.

Install the certbot client for Let’s Encrypt.

certbot is a command line utility to request a free SSL certificate for your domain from Let’s Encrypt, to secure your traffic using HTTPS. The certbot package is not officially available on Debian, so you will first need to add an additional repository to download it using apt-get.

Before you proceed, ensure you have a subdomain/domain name pointed at your server. Let’s Encrypt’s policy is to not issue SSL certificates to bare IP addresses, so go to your domain registrar or DNS provider to set up an “A” record with your desired hostname (for example, docker.example.com) pointed at your IP address.

echo 'deb http://ftp.debian.org/debian jessie-backports main' | tee /etc/apt/sources.list.d/backports.list

apt-get update

apt-get install certbot -t jessie-backports

Obtain a signed certificate and configure nginx for HTTPS.

Using certbot, request a signed certificate from Let’s Encrypt using the following command. Follow the prompts in the wizard and once generated, the certificates will be stored in the /etc/letsencrypt/live/example.com/ directory as two files: fullchain.pem and privkey.pem.

certbot certonly -a webroot --webroot-path=/usr/share/nginx/html -d domain.com

Now, you will need to edit the default.conf file once again to enable listening on port 443 (TLS), force all HTTP (insecure) traffic to HTTPS (secure), and let Nginx know where on your server to find the signed SSL certificates.

Run nano /etc/nginx/conf.d/default.conf to open the text editor, then copy & paste the following:

upstream portainer {
server 172.18.0.2:9000;
}

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name domain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;

ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

root /usr/share/nginx/html;
index index.html index.htm;

location / {
try_files $uri $uri/ /index.html;
}

location /portainer/ {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://portainer/;
}

location /portainer/api/websocket/ {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_pass http://portainer/api/websocket/;
}

}

Save and close the file. As in the previous step, execute nginx -t to test that you have not made any syntax errors while editing the configuration file, then assuming there are no errors, nginx -s reload to apply the nginx configuration.

Exit the nginx container’s Bash shell and test.

If you followed all of the steps correctly, you should now have a functioning setup of Portainer behind an nginx reverse proxy with SSL/TLS. Assuming you are still in the Bash terminal for your container, type exit to return to the shell for your host machine.

Browse to https://example.com/portainer and make sure you see the padlock symbol in your browser signifying that the connection is secure. If you try to access http://example.com/portainer, you should be automatically redirected to the HTTPS version.

Now you can proceed to set an admin password and use Portainer to easily manage your Docker host! We hope you found this tutorial useful. Feel free to message us with any questions or let us know your feedback. If you think your colleagues would benefit from this information, please don’t hesitate to share the link to this resource.