Portainer is an open-source container management solution for Docker, Kubernetes, and Nomad that simplifies starting, creating and running containers in an easy way. It provides a web-based dashboard to manage containers, images, networks, and volumes.

In this tutorial, you will learn to install and configure the Portainer container management solution on a Linux server and use it to create and manage Docker containers to run different apps. You will also learn to put these containers behind Nginx using the Nginx proxy manager.

Prerequisites

  • A Linux server running Ubuntu / Debian / Cent OS / Rocky Linux 8 / Alma Linux.
  • A non-root user with sudo privileges.
  • A Fully Qualified Domain Name (FQDN) pointing to the server for Portainer (portrainer.example.com) and Nginx Proxy Manager (npm.example.com).

Step 1 – Configure Firewall

Cent OS/Rocky Linux/Alma Linux

You should have the Firewalld firewall installed. Check the firewall’s status.

$ sudo firewall-cmd --state
running

Open ports 80, 9443 and 443. Portainer uses port 9443 to expose its web UI via HTTPS. Nginx Proxy Manager uses port 81 for its UI.

$ sudo firewall-cmd --permanent --add-service=http
$ sudo firewall-cmd --permanent --add-service=https
$ sudo firewall-cmd --permanent --add-port=9443/tcp
$ sudo firewall-cmd --permanent --add-port=81/tcp

Reload the firewall to enable the changes.

$ sudo firewall-cmd --reload

Ubuntu/Debian

Ubuntu and Debian systems use ufw (Uncomplicated Firewall) by default.

Check if the firewall is running.

$ sudo ufw status

If it is running, then open ports 80, 9443 and 443.

$ sudo ufw allow 80
$ sudo ufw allow 443
$ sudo ufw allow 9443
$ sudo ufw allow 81

Open the SSH port if the firewall isn’t running.

$ sudo ufw allow "OpenSSH"

Enable the firewall if it is not running.

$ sudo ufw enable

If it is running, reload it to apply the changes.

$ sudo ufw reload

Step 2 – Install Docker

Cent OS/Rocky Linux/Alma Linux

Run the following command to install Docker.

$ sudo yum install -y yum-utils
$ sudo yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce docker-ce-cli containerd.io

Ubuntu

$ sudo apt install ca-certificates curl gnupg lsb-release
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu 
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io

Debian

$ sudo apt install ca-certificates curl gnupg lsb-release
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian 
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io

Enable and Start the Docker service.

$ sudo systemctl start docker --now

Add your username to the Docker group.

$ sudo usermod -aG docker $USER

Log out of the system and log back in to apply the change.

Step 3 – Install Docker Compose

Download and install Docker compose binary.

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Apply executable permission to the binary.

$ sudo chmod  x /usr/local/bin/docker-compose

Step 4 – Install Portainer

Create a directory for Portainer.

$ mkdir ~/portainer

Switch to the directory.

$ cd ~/portainer

Create and open the Docker Compose file for editing.

$ nano docker-compose.yaml

Paste the following code in it.

version: "3.3"
services:
    portainer:
      image: portainer/portainer-ce:latest
      container_name: portainer
      restart: always
      privileged: true
      volumes:
        - ./data:/data:Z
        - /var/run/docker.sock:/var/run/docker.sock:Z
      ports:
        - 9443:9443

Save the file by pressing Ctrl X and entering Y when prompted.

Let us go through the Docker compose file.

  • We are pulling the latest version of the Portainer Community Edition from Docker Hub. Portainer Community Edition is free to use, while their Business edition requires a paid license. You can pull the Business Edition, but you will be asked for the license key to use it.
  • We have named our container as portainer for identification and linking purposes.
  • The restart policy is set to always so that the container remains up during boot up.
  • The privileged: true setting is for Portainer to be able to access the Docker socket and run in a privileged context since we are using SELinux. If you are not using SELinux, you can remove this setting. This setting gives Portainer container access to everything on the host system, including access to the hardware. So, enable this setting only when you know what you are doing.
  • The volumes section maps the folder on the host to the folders in the container using Bind mounts. We have exposed the directory ~/portainer/data to the container for storing any relevant data and Docker socket API for container management. The :Z label tells Docker that we are running SELinux on our host. If you don’t have SELinux enabled, you should remove the label.

Start Portainer.

$ docker-compose up -d

Check the status of the container.

$ docker ps
CONTAINER ID   IMAGE                           COMMAND        CREATED         STATUS         PORTS                                                           NAMES
916411e8d12e   portainer/portainer-ce:latest   "https://www.howtoforge.com/portainer"   5 seconds ago   Up 4 seconds   8000/tcp, 9000/tcp, 0.0.0.0:9443->9443/tcp, :::9443->9443/tcp   portainer

Step 5 – Access and Configure Portainer

Open the URL https://:9443 in your browser, and you will get the following screen.

<img alt="Portainer Installation Screen" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-installation-screen.png626ac6164006b.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="700" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

You will be asked to create a new administrator user. Add your user details. Uncheck the box Allow collection of anonymous statistics if you care about privacy. Click the Create user button to start the installation and create a new administrator account.

Next, you will be taken to the following dashboard screen.

<img alt="Portainer Dashboard" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-dashboard.png626ac61669852.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="541" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

After a few seconds, it will automatically refresh and show you the following screen.

<img alt="Portainer Home" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-home.png626ac61692fb3.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="540" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

It will show you the local environment in which Portainer is running. Click on the local environment to get started.

<img alt="Portainer Environment Homepage" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-environment-homepage.png626ac616b2967.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="540" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Most of the sections are self-explanatory. The Stacks section helps in creating containers using Docker compose files. You can deploy containers directly using the Containers category in the sidebar. You can configure the current docker environment through the Hosts section. The App Templates section comes with pre-installed Docker compose files for installing the most common applications. You can also create custom templates.Advertisement

The Settings section allows you to configure various settings such as adding custom Docker registries, adding multiple hosts for Docker swarm, configuring user access, backing up data, and customizing Portainer.

Step 5 – Put Portainer behind a reverse proxy using Nginx Proxy Manager (NPM)

Before moving ahead, let us put Portainer behind a reverse proxy using Nginx Proxy Manager. Nginx Proxy Manager is a Docker application that provides a web management UI for setting up Nginx as a reverse proxy host. It can also be used as a redirect or a streaming host.

Install NPM

The first step is to create a network for Nginx Proxy Manager (NPM). Open the Networks section and click the button Add Network to create a new network.

<img alt="Portainer Networks List" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-networks-list.png626ac616d51a0.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="470" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”494″>

Give a name to the network and leave all the settings unchanged. Click the Create the network button to finish.

<img alt="Portainer Create Network Page" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-create-network-page.png626ac616f2e99.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="469" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”495″>

Visit the Stacks and create a new stack using the Add stack button.

<img alt="Portainer Stacks Page" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-stacks-page.png626ac61728eff.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="289" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”640″>

Name the stack as nginx-proxy-manager and paste the following code into it.

version: "3.3"
services:
  npm-app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: npm-app
    restart: unless-stopped
    ports:
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP
    environment:
      DB_MYSQL_HOST: "npm-db"
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: "npm"
      DB_MYSQL_PASSWORD: ${DB_MYSQL_PASSWORD}
      DB_MYSQL_NAME: "npm"
      # Uncomment the line below if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    volumes:
      - ./npm-data:/data:Z
      - ./letsencrypt:/etc/letsencrypt:Z
    depends_on:
      - npm-db
    networks:
      - npm-network
      - npm-internal

  npm-db:
    image: 'mariadb:latest'
    container_name: npm-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: 'npm'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD: ${DB_MYSQL_PASSWORD}
    volumes:
      - ./npm-data/mysql:/var/lib/mysql:Z
    networks:
      - npm-internal

networks:
  npm-internal:
  npm-network:
    external: true

<img alt="Portainer Add Stack Page" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-add-stack-page.png626ac6174d20c.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="705" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

We have set two environment variables to set database and root MySQL passwords. Portainer can be used to set secrets using environment variables. Scroll down the page and click the Add environment variable button to add strong passwords.

<img alt="Portainer Stack Environment Variables" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-stack-environment-variables.png626ac6177b329.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="424" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Click the Deploy the stack button to create and start the NPM container.

Access NPM

Open the URL https://:81 in your browser, and you will get the following screen. Enter the following default credentials to sign in.

Email address: [email protected] Password: changeme

Next, you will be immediately asked to set a name and an email address. Click the Save button, and you will be asked to create a new password. Click the Save button again to get started.

<img alt="Nginx Proxy Manager Dashboard" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/nginx-proxy-manager-dashboard.png626ac617a5eb5.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="277" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Visit the Hosts >> Proxy Hosts and click the Add Proxy Host button.

<img alt="Add Portainer as Proxy Host" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/add-portainer-proxy-host.png626ac617cb5d0.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="548" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”494″>

Enter the domain name as portainer.example.com. Choose the scheme as https. Enter the name of the container as the Forward Hostname and 9443 as the Forward port. Check the options Block Common Exploits and Websockets Support options.

<img alt="Portainer NPM SSL options" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-npm-ssl-options.png626ac617ec4fc.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="552" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”495″>

Switch to the SSL tab and select Request a new SSL Certificate from the dropdown menu. Check the Force SSL and HTTP/2 Support options to secure and optimize your SSL connection. Enter the email address to receive renewal notifications and agree to the terms of service. Click the Save button to finish setting up the Proxy Host for Portainer.

Connect Portainer to the NPM Container

We have set up the Proxy host, but the container is still not connected to the NPM network. Go back to the Portainer dashboard, visit the Containers section, and select the portainer container.

Select npm-network from the dropdown menu under the Connected networks section and click the Join network button to add the Portainer container to the proxy manager’s network.

<img alt="Portainer Connected Networks" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-connected-networks.png626ac6182260e.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="421" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”722″>

You might get an error but refresh the page, and you should see the container added to the NPM network.

<img alt="Portainer Connected to NPM" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-connected-to-npm.png626ac6185105e.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="285" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”656″>

You should be able to access Portainer using the URL https://portainer.example.com in your browser.

You can follow a similar procedure to put NPM behind a publicly accessible URL like, https://npm.example.com as discussed in our Nginx Proxy Manager tutorial.

Now that you have set a public URL for Portainer, you can remove the exposed 9443 port. To do that, go back to the Terminal and switch to the portainer directory.

$ cd ~/portainer

Open the Docker compose file for editing.

$ nano docker-compose.yaml

Remove the ports section by commenting it out, as shown below.

version: "3.3"
services:
    portainer:
      image: portainer/portainer-ce:latest
      container_name: portainer
      restart: always
      privileged: true
      volumes:
        - ./data:/data:Z
        - /var/run/docker.sock:/var/run/docker.sock:Z
      #ports:
      #  - 9443:9443
      networks:
        - npm-network

networks:
  npm-network:
    external: true

Save the file by pressing Ctrl X and entering Y when prompted.

Here, we have added the details of the NPM network because we will need to restart the Portainer container.

Stop the Portainer container.

$ docker-compose down --remove-orphans

Start the container again with the updated configuration.

$ docker-compose up -d

Step 6 – Deploy a container using App Template

Portainer provides several pre-defined templates to launch applications directly with minimal configuration.

<img alt="Portainer App Templates" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-app-templates.png626ac61876e2f.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="540" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Visit the App Templates section and select any template. Give it a name, and select the network for use. Use the advanced options section to deploy custom ports, networks, and volume mounts.

Click the Deploy the container button to finish deploying your application. Here we are deploying the Redis container.

<img alt="Portainer Redis App Deployment" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-redis-app-deployment.png626ac618a57f2.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="479" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Step 7 – Manage Containers

Let us manage an existing container. Open the containers page, and you will see all the running containers.

<img alt="Portainer Containers List" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-containers-list.png626ac618c1f48.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="384" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Click the recently created hw-redis container to proceed.

<img alt="Portainer Container Actions" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-container-actions.png626ac618e8353.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="515" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

On top, you can see a list of actions you can perform on a running container. You can stop and kill the container. Recreate will create the container from scratch. The Duplicate/Edit option will allow you to create another identical container allowing you the ability to change settings before launching it.

The Container status shows the running time, IP address and other details about the container.

The Logs option shows the output of the docker logs command. Since the command’s output is not cached, every time you refresh the page, the command is run from scratch.

<img alt="Portainer Container Logs" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-container-logs.png626ac61913fdd.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="587" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

The Inspect option runs the command docker inspect on the container and shows its output.

<img alt="Portainer Container Inspect" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-container-inspect.png626ac6193fe59.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="504" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

The Stats option shows you the usage of the container in real-time.

<img alt="Portainer Container Stats" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-container-stats.png626ac61959552.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="612" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

You can launch the Container console by using the Console option. You will be asked for the command and the system user to run.

<img alt="Portainer Container Console Configuration" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-container-console-config.png626ac61978add.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="365" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”531″>

Press the Connect button to launch the console.

<img alt="Portainer Container Console Shell" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-container-console-shell.png626ac61993a77.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="284" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”643″>

The Attach option runs the docker attach command.

There are other options on the Container details page. You can create an image using an existing container. Other options include changing a container’s restart policy and connecting or disconnecting a network to an existing container.

Attach an Outside container to Portainer

Creating any container outside of Portainer will show up inside it as long as it is created on the same system Portainer is running. This is possible because Portainer is hooked up with Docker using the websocket.

Let us test by running the Hello World Docker container.

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:10d7d58d5ebd2a652f4d93fdd86da8f265f5318c6a73cc5b6a9798ff6d2b2e67
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Check the container list in the terminal. We are using the -a flag to show the list of all containers, including the stopped ones. You can see the name of the container as sad_williamson.

$ docker ps -a
CONTAINER ID   IMAGE                             COMMAND                  CREATED         STATUS                     PORTS                                                                                  NAMES
5fa46b85d594   hello-world                       "https://www.howtoforge.com/hello"                 3 minutes ago   Exited (0) 3 minutes ago                                                                                          sad_williamson
.....

Now, check the Portainer Containers page, and the hello world container will show up as stopped in the list with the same name.

<img alt="Portainer Hello World Container" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/04/echo/portainer-hello-world-container.png626ac619bb548.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="461" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”745″>

Conclusion

This concludes our tutorial on installing and using Portainer for Docker management and Nginx Proxy Manager. We will explore building docker images, creating custom containers and using Portainer with Docker swarm in an upcoming tutorial. If you have any questions, post them in the comments below.