...

How to Install Portainer on a VPS and Manage Docker Containers

Martin Klein

Reading time 1 minute

Portainer is a web-based dashboard for managing Docker containers, images, networks, volumes, and Docker Compose stacks without having to work constantly from the command line. In this guide, Portainer will be installed on a VPS running Ubuntu, launched in a separate container, and connected to the local Docker Engine.

For secure access to the dashboard, a dedicated domain, Nginx as a reverse proxy, and a Let’s Encrypt HTTPS certificate will be configured. The administrative interface will not need to be exposed directly to the internet through Portainer’s default port.

After installation, the following basic operations will be performed through the web interface:

  • Starting a standalone Docker container;
  • Deploying an application as a Docker Compose stack;
  • Viewing logs and restarting containers;
  • Creating a Docker network and a volume;
  • Updating containers and stacks;
  • Backing up the Portainer configuration;
  • Verifying automatic startup after the VPS is rebooted.

The result will be a secure Docker management dashboard available at an address such as: https://portainer.example.com

Portainer settings will be stored in a dedicated Docker volume, so the container can be updated or recreated without losing the configuration.

How Portainer Works

Portainer is a web interface for managing a Docker environment. It connects to Docker Engine on the server and lets you perform core operations with containers, images, networks, and volumes through a browser.

In this guide, Portainer will run on the same VPS as Docker. Access to the dashboard will be configured through a dedicated domain, Nginx, and HTTPS. The administrative interface itself will not need to be exposed directly through Portainer’s default port.

What Portainer Is Used For

With Portainer, you can:

  • Create, start, stop, and restart containers;
  • Pull Docker images and remove unused versions;
  • View container logs and statistics;
  • Create Docker networks and connect services to them;
  • Create and attach volumes for persistent data storage;
  • Deploy Docker Compose configurations as Stacks;
  • Update and recreate containers;
  • Manage multiple Docker environments from a single dashboard.

Portainer does not replace Docker Engine. It runs on top of the Docker API and provides a graphical interface for the same operations that can be performed using the docker and docker compose commands.

Docker, Portainer, Nginx, and HTTPS Architecture

In the final configuration, the components will interact as follows:

  1. Docker Engine runs Portainer and the other containers.
  2. Portainer connects to the local Docker Engine through the /var/run/docker.sock socket.
  3. Portainer accepts requests only on the VPS’s local interface.
  4. Nginx accepts external requests for the domain and forwards them to Portainer.
  5. Certbot issues an SSL certificate for the domain.
  6. The user opens the dashboard over a secure HTTPS connection.

The architecture will look like this:

Browser

│ HTTPS

Nginx

│ reverse proxy

Portainer

│ Docker socket

Docker Engine

├── Containers

├── Images

├── Networks

├── Volumes

└── Stacks

This approach avoids exposing the Portainer administrative port directly to the internet while using the standard ports 80 and 443.

Preparing the VPS

The installation requires a VPS running Ubuntu, with a public IP address and SSH access. For a test installation, a server with 2 vCPUs, 2–4 GB of RAM, and at least 20 GB of disk space is sufficient.

You will also need the following prepared in advance:

  • A domain or subdomain;
  • An SSH key;
  • Open ports 22, 80, and 443;
  • A user with sudo privileges.

You can then proceed with creating the VM.

Creating a Virtual Machine

Create a new virtual machine in the cloud control panel. Select Ubuntu 24.04 LTS as the operating system.

For a test configuration, you can use:

  • Operating system: Ubuntu 24.04 LTS
  • Processor: 2 vCPU
  • RAM: 4 GB
  • Disk: 20 GB

Attach an existing network and security group. The inbound traffic rules must allow:

  • TCP 22 — SSH
  • TCP 80 — HTTP
  • TCP 443 — HTTPS

You do not need to open Portainer ports 9000 and 9443 in the security group. Access to the panel will be routed through Nginx.

When creating the VM, select an existing SSH key pair or create a new one. The private key must be saved on your local computer.

Attaching a Public IP Address

After starting the VM, attach a Floating IP or another public IPv4 address to it.

The virtual machine will have two addresses:

  • A private IP address for use within the cloud network;
  • A public IP address for SSH connections and access to Portainer via the domain.

Make sure the VM has the Active status and that the public address is displayed in the control panel.

Connecting via SSH and Updating Ubuntu

On Windows, open Command Prompt or PowerShell and change to the directory containing your private SSH key: cd C:\Users\Username\Downloads

Connect to the server: ssh -i .\portainer-guide.pem ubuntu@PUBLIC_IP

Replace portainer-guide.pem with the name of your key, and PUBLIC_IP with the public IP address of the VPS.

On the first connection, SSH will display the server’s fingerprint. After verifying it, confirm the connection: yes

Update the package index and installed components:

sudo apt update

sudo apt upgrade -y

Install the basic packages: sudo apt install -y ca-certificates curl gnupg unzip ufw

After the update, the server is ready for Docker Engine and Portainer installation.

Installing Docker and Portainer

Portainer runs as a Docker container, so Docker Engine must first be installed on the VPS. The Docker Compose Plugin is also required to manage Compose configurations.

Installing Docker Engine and the Compose Plugin

Add Docker’s official GPG key:

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \

-o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc

Create the official APT repository file:

sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF

Types: deb

URIs: https://download.docker.com/linux/ubuntu

Suites: $(. /etc/os-release && echo &quot;${UBUNTU_CODENAME:-$VERSION_CODENAME}&quot;)

Components: stable

Architectures: $(dpkg –print-architecture)

Signed-By: /etc/apt/keyrings/docker.asc

EOF

Update the package index: sudo apt update

Install Docker Engine, Docker CLI, containerd, Buildx, and the Compose Plugin:

sudo apt install -y \

docker-ce \

docker-ce-cli \

containerd.io \

docker-buildx-plugin \

docker-compose-plugin

This is the recommended way to install Docker on Ubuntu: the packages will be retrieved from the official repository and can be updated via APT.

Enable Docker and configure the service to start automatically: sudo systemctl enable –now docker

To allow the ubuntu user to run Docker commands without sudo, add the user to the docker group: sudo usermod -aG docker ubuntu

Exit the SSH session: exit

Reconnect to the VPS and verify the installation:

docker –version

docker compose version

systemctl is-active docker

systemctl is-enabled docker

The output should show the Docker and Docker Compose versions, as well as the following statuses:

active

enabled

If needed, also run a test container: docker run –rm hello-world

Docker’s official documentation recommends using the hello-world image to verify that the engine was installed successfully.

Creating a Volume and Starting Portainer

Portainer settings, user accounts, and information about connected environments must be stored independently of the container so they are not deleted when the container is recreated or updated. To do this, create a named Docker volume: docker volume create portainer_data

Start Portainer Community Edition:

docker run -d \

–name portainer \

–restart=always \

-p 127.0.0.1:9000:9000 \

-v /var/run/docker.sock:/var/run/docker.sock \

-v portainer_data:/data \

portainer/portainer-ce:lts

In this configuration:

  • /var/run/docker.sock gives Portainer access to the local Docker Engine;
  • portainer_data stores the dashboard’s persistent configuration;
  • –restart=always restarts the container after the VPS is rebooted;
  • port 9000 is accessible only through 127.0.0.1 and is not published directly to the internet.

Current versions of Portainer use HTTPS port 9443 by default, but HTTP port 9000 can be enabled for use behind a reverse proxy. In this guide, the external TLS connection will be terminated by Nginx, while the connection from Nginx to Portainer will remain local.

Check the container status: docker ps –filter name=portainer

Check the created volume: docker volume ls –filter name=portainer_data

Make sure Portainer responds locally: curl -I http://127.0.0.1:9000

The response may contain status code 200, 301, 302, or 307. The key point is that the connection is established and no Connection refused error occurs.

Configuring a Domain and HTTPS

You can access the panel through a local port, but for regular use it is better to configure a dedicated subdomain and secure the connection with a Let’s Encrypt certificate.

This example uses the following address: portainer.example.com

Replace it with your own domain in all commands and configuration files.

Creating a DNS record

In the DNS control panel, create a record with the following settings:

Type: A

Name: portainer

IPv4 address: PUBLIC_IP

TTL: Auto

Proxy status: DNS only

Replace PUBLIC_IP with the public IP address of the VPS.

If DNS is managed through Cloudflare, it is best to leave the record in DNS only mode while the certificate is being issued. After saving the record, check it on your local computer: nslookup portainer.example.com

The response should show the public IP address of the VPS.

DNS updates may take some time. Proceed with certificate issuance only after the domain name starts resolving to the correct IP address.

Configuring Nginx as a reverse proxy

Install Nginx: sudo apt install -y nginx

Enable the service to start on boot and start it now: sudo systemctl enable –now nginx

Create the site configuration: sudo nano /etc/nginx/sites-available/portainer

Add the following server block:

server {

listen 80;

listen [::]:80;

server_name portainer.example.com;

access_log /var/log/nginx/portainer_access.log;

error_log /var/log/nginx/portainer_error.log;

location / {

proxy_pass http://127.0.0.1:9000;

proxy_http_version 1.1;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header X-Forwarded-Proto $scheme;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection &quot;upgrade&quot;;

proxy_read_timeout 3600s;

proxy_send_timeout 3600s;

proxy_buffering off;

client_max_body_size 0;

}

}

Replace portainer.example.com with your own subdomain.

Portainer officially supports running behind an Nginx reverse proxy. The Upgrade and Connection headers are required for WebSocket connections to work correctly.

Enable the configuration:

sudo ln -s /etc/nginx/sites-available/portainer \

/etc/nginx/sites-enabled/portainer

Remove the default Nginx site: sudo rm -f /etc/nginx/sites-enabled/default

Check the syntax: sudo nginx -t

If the check succeeds, you will see:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

nginx: configuration file /etc/nginx/nginx.conf test is successful

Apply the changes: sudo systemctl reload nginx

Test the reverse proxy locally: curl -I -H &quot;Host: portainer.example.com&quot; http://127.0.0.1

After that, the panel should be accessible over HTTP: http://portainer.example.com

Issuing an SSL Certificate

Install Certbot via Snap: sudo snap install –classic certbot

Create a symbolic link: sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

If the link already exists, the command may return a corresponding warning. In that case, you do not need to create it again.

Run certificate issuance: sudo certbot –nginx -d portainer.example.com

Certbot will validate the domain, issue the certificate, and automatically add the HTTPS settings to the Nginx configuration. The official Certbot instructions recommend installing it via Snap and support automatic changes to the Nginx configuration using the –nginx option.

During installation, you will need to:

  1. Provide an email address;
  2. Accept the terms of service;
  3. Choose whether to subscribe to the mailing list;
  4. Confirm HTTP-to-HTTPS redirection if prompted.

Check the configuration after the certificate has been issued: sudo nginx -t

Test automatic renewal with a dry run: sudo certbot renew –dry-run

Certbot installs an automatic certificate renewal mechanism, and the renew –dry-run command lets you test it without performing an actual renewal.

Open the panel: https://portainer.example.com

The browser should not display any certificate warnings. The page will show either the initial Portainer setup form or the sign-in form for an existing account.

First Login and Securing Portainer

After configuring the domain, open Portainer in your browser: https://portainer.example.com

On first launch, the service prompts you to create an administrator account. It is best to do this right away: if the initial setup remains incomplete for too long, Portainer may lock the installation session, after which the container will need to be restarted.

Creating an Administrator

On the initial setup page, enter the administrator name and a strong password.

We recommend using a password that is at least 12–16 characters long and includes:

  • Uppercase and lowercase letters;
  • Digits;
  • Special characters;
  • A unique combination that is not used for any other services.

You can keep the default admin username; however, using a separate, unpredictable username makes credential brute-forcing more difficult.

After completing the form, click the button to create the user.

Connecting the Local Docker Environment

Portainer started with the following mount:

/var/run/docker.sock:/var/run/docker.sock

gains access to the Docker Engine on the same VPS. This environment is usually created automatically during initial setup and is displayed under the name local.

After logging in, open the section Environments or the main page Home. A local Docker environment with the status Up should appear in the list.

Open it to start managing the server resources. The following sections should be available in the dashboard:

  • Containers;
  • Images;
  • Networks;
  • Volumes;
  • Stacks.

Portainer refers to a managed Docker host as an Environment. In addition to the local server, you can connect additional Docker Standalone, Docker Swarm, and other supported environments to the same dashboard.

If the local environment does not appear, verify that the Docker socket is actually mounted into the container:

docker inspect portainer \

–format ‘{{json .Mounts}}’

The output should include:

/var/run/docker.sock

portainer_data

Also check the container status:

docker ps –filter name=portainer

docker logs –tail=50 portainer

Next, we will discuss securing the management dashboard.

How to secure the control panel

Portainer provides full access to the Docker host, so compromising an administrative account effectively gives an attacker control over the containers and data on the VPS.

For basic panel security:

  • Use a unique, strong password;
  • Allow access only over HTTPS;
  • Do not expose ports 9000 and 9443 to the public internet;
  • Keep Portainer, Docker, and Ubuntu up to date;
  • Restrict access to the panel by IP address using a firewall or Nginx if it is needed only by administrators;
  • Ideally, use a VPN or configure authentication in Nginx, for example certificate-based authentication (mTLS);
  • Do not grant global administrator privileges to regular users;
  • Create regular backups of portainer_data.

In the current configuration, port 9000 is bound to 127.0.0.1, so it is not directly accessible from outside. Nginx accepts external requests on port 443 and forwards them to Portainer over a local connection.

If necessary, you can further restrict access in the Nginx server block:

allow 203.0.113.25;

deny all;

Here, 203.0.113.25 must be replaced with the public IP address of the administrator’s computer. This approach is suitable only if the administrator has a static IP address.

Launching a Container Through Portainer

To test the panel, we will start a simple Nginx container from the web interface. It will display the default test page and start automatically after Docker is restarted.

Creating and starting a test container

Open the local environment and go to: Containers → Add container

Fill in the main fields:

Name: portainer-demo

Image: nginx:alpine

Under Network ports configuration , click Publish a new network port and specify:

Host: 8080

Container: 80

Protocol: TCP

Portainer lets you assign a port automatically or manually map the host port to the container port.

Scroll down to the block Advanced container settings and open the tab Restart policy. Select: Unless stopped

This policy restarts the container after a failure and when Docker starts, except when an administrator has stopped it manually.

Click Deploy the container.

After the container starts, Portainer will return you to the container list. For portainer-demo, you should see:

State: running

Image: nginx:alpine

Published ports: 0.0.0.0:8080 → 80/tcp

For an additional check, open: http://PUBLIC_IP:8080

However, this URL will work only if port 8080 is open in the cloud firewall. For this guide, you do not need to open this port: you can check the container status through Portainer or on the VPS: curl -I http://127.0.0.1:8080

Expected response: HTTP/1.1 200 OK

Viewing logs and restarting the container

In the section Containers click the name portainer-demo, then open Logs.

Portainer will display the container’s standard output. For Nginx, log entries will appear after you access its page: curl http://127.0.0.1:8080 > /dev/null

Refresh the log in Portainer. It should now contain a line with the HTTP request and the 200 response code.

Portainer allows you to limit the number of displayed lines, enable automatic refresh, and view timestamps. The log is accessed via Containers → container name → Logs.

To restart the container, return to its page and click Restart. The same operation can be performed from the general list of containers by selecting the required container and choosing the corresponding action.

After the restart, make sure the status has changed back to: running

You can also verify the result via SSH: docker ps –filter name=portainer-demo

To stop it, use the Stop and Startbuttons. You do not need to delete the container at this stage — it will be useful for a later demonstration of networks and volumes.

Deploying a Docker Compose Stack

In Portainer, Docker Compose configurations are deployed as Stacks. A Stack combines related services, networks, and volumes into a single managed object. You can create one in the web editor, upload it from a file, or connect it to a Git repository.

Creating a Stack via the Web Editor

Open the local Docker environment and go to: Stacks → Add stack

Enter the name: portainer-demo-stack

Select the creation method: Web editor

Paste the following configuration into the editor:

services:

web:

image: nginx:alpine

container_name: stack-web

restart: unless-stopped

ports:

– &quot;127.0.0.1:8081:80&quot;

networks:

– stack-network

redis:

image: redis:alpine

container_name: stack-redis

restart: unless-stopped

volumes:

– redis-data:/data

networks:

– stack-network

networks:

stack-network:

driver: bridge

volumes:

redis-data:

This configuration creates:

  • The Nginx container stack-web;
  • The Redis container stack-redis;
  • A separate bridge network;
  • A volume for Redis data;
  • Local port 8081 for testing Nginx.

The port is bound to 127.0.0.1, so it is not exposed directly to the internet.

Click: Deploy the stack

Portainer will pull the required images and create the services, network, and volume. After the process completes, open the newly created Stack. Both containers should have the status running.

You can test Nginx via SSH: curl -I http://127.0.0.1:8081

Expected response: HTTP/1.1 200 OK

Portainer supports creating a Stack through the Web Editor, by uploading a Compose file, from a Git repository, or from a template. This example uses the Web Editor because it lets you paste the configuration directly into the panel.

Modifying and redeploying the Stack

Open: Stacks → portainer-demo-stack → Editor

Change the Nginx image, for example: image: nginx:stable-alpine

Alternatively, add an environment variable for Redis:

environment:

ALLOW_EMPTY_PASSWORD: &quot;yes&quot;

For demonstration purposes, it is safer to limit the change to replacing the Nginx image and not modify the Redis settings.

After making the change, click: Update the stack

If Portainer prompts you to pull the images again, enable the option: Re-pull image

Portainer will apply the new Compose configuration and recreate the modified containers if necessary. The Editor section is available for Stacks created through the Web Editor or uploaded from a file.

After the update, check: docker ps –filter name=stack-

Also repeat the request: curl -I http://127.0.0.1:8081

If both containers are running, the redeployment was successful.

Managing Networks and Volumes

Docker networks handle communication between containers, the host, and external connections, while volumes store data outside the container’s file system. Portainer lets you create and attach these resources through the web interface.

Creating a Docker network

Open the local environment and go to: Networks → Add network

Specify:

Name: portainer-demo-network

Driver: bridge

You can leave the remaining network settings blank. Docker will automatically assign a subnet and gateway.

To allow running containers to be attached to the network, enable: Enable manual container attachment

Click: Create the network

Portainer will create a custom bridge network. It will appear in the main list alongside the default bridge, host, and none networks. If the IPv4 settings are left blank, Docker assigns an address range automatically.

To connect the test container portainer-demo, open: Containers → portainer-demo → Duplicate/Edit

In the network settings section, select: portainer-demo-network

Then deploy the modified copy of the container. Depending on the Portainer interface version, you may also be able to attach the network from the container page without fully recreating the container.

You can verify the connection via SSH with the command: docker network inspect portainer-demo-network

portainer-demo should be displayed in the Containers section.

Do not delete the network while containers are attached to it. First disconnect or recreate the associated services.

Creating and Mounting a Volume

Open: Volumes → Add volume

Specify the name: portainer-demo-data

For local storage, leave the default driver: local

Click: Create the volume

Portainer lets you create volumes and then mount them to containers for persistent data storage.

Now open: Containers → portainer-demo → Duplicate/Edit

Go to the tab Volumes and click: Map additional volume

Select the volume you created: portainer-demo-data

Specify the following path inside the container: /usr/share/nginx/html

However, an empty volume will mask the default Nginx files, so before mounting it, it is better to create a test page in it first using a temporary container:

docker run –rm \

-v portainer-demo-data:/data \

alpine sh -c \

‘echo &quot;<h1>Portainer Volume Demo</h1>&quot; > /data/index.html’

After that, mount the volume to the container and confirm recreation.

Check the result: curl http://127.0.0.1:8080

The response should include: <h1>Portainer Volume Demo</h1>

Portainer mounts a volume to an existing container through the Duplicate/Edit operation because changing mounts requires recreating the container.

Updating Containers

Containers are usually not updated “in place.” To move to a new version, pull the latest image and recreate the container with the same settings, networks, and mounted volumes.

Before updating, review the image documentation, the changelog, and the compatibility of the new version. For services with persistent data, it is also recommended to create a backup in advance.

Pulling a New Image Version

Open the local environment and navigate to Images.

In the image field, specify the required repository and tag. For example: nginx:stable-alpine

Click Pull the image.

Portainer will pull the image from Docker Hub or another connected registry. The new image will appear in the Images list, but the running container will continue to use the previous version until it is recreated.

Replacing a fixed tag with latest without checking it first is not recommended: the contents of such a tag may change, making redeployment less predictable.

Recreating a Container Without Losing Data

Open: Containers → portainer-demo → Duplicate/Edit

In the Image field, specify the new image: nginx:stable-alpine

Verify that the following settings have been preserved:

  • published port 8080;
  • restart policy: Unless stopped;
  • the portainer-demo-network network;
  • the portainer-demo-data volume mounted at /usr/share/nginx/html.

Click: Deploy the container

Portainer will warn you that a container with this name already exists and prompt you to replace it. Confirm the recreation.

When the container is recreated, the old container is removed, but the named volume remains on the server. Therefore, the Portainer Volume Demo page stored in portainer-demo-data should not disappear.

Check the result: curl http://127.0.0.1:8080

You can also check which image is being used:

docker inspect portainer-demo \

–format ‘{{.Config.Image}}’

Expected result: nginx:stable-alpine

Data persistence depends specifically on a volume or bind mount. Data written only to the container’s internal layer will be lost when the container is removed.

Updating a Docker Compose stack

Open: Stacks → portainer-demo-stack → Editor

Change the tag of the required image. For example:

services:

web:

image: nginx:stable-alpine

Before updating, verify that the volumes section is still present in the configuration:

services:

redis:

volumes:

– redis-data:/data

volumes:

redis-data:

Click: Update the stack

To force the download of up-to-date images, enable the option Re-pull image. It makes Portainer pull the images again before deploying the Stack.

After the update, check the service status: docker ps –filter name=stack-

Also verify that the test Nginx instance is reachable: curl -I http://127.0.0.1:8081

The named volume redis-data is not removed during a standard stack update, so the Redis data is preserved. However, before performing a major application or database update, you should review the migration requirements separately.

Backing Up Portainer

Portainer stores its configuration in the /data directory, which in this guide is mounted as the named volume portainer_data.

The interface includes a built-in backup mechanism. It creates a configuration archive that can later be uploaded when deploying a new Portainer instance.

What to Back Up

A Portainer backup includes its internal database and configuration, including:

  • Users and access settings;
  • Connected environments;
  • Registry settings;
  • Dashboard configuration;
  • Stack definitions created through Portainer;
  • Associated metadata and settings.

However, such an archive does not contain the Docker containers themselves, images, and user data from volumes or bind mounts. These must be backed up separately.

Creating a Configuration Backup

Log in to Portainer with administrator privileges and open: Settings

Scroll down to the section: Back up Portainer

Leave the option set to: Download backup file

If needed, enable: Password protect

Enter a strong password and store it in a secure location. Without this password, the encrypted archive cannot be restored.

Click: Download backup

The browser will download an archive in .tar.gz format. It contains the data that Portainer stores in /data.

Store a copy of the archive outside the VPS, for example in secure cloud storage or on a local drive. If the backup remains only on the same server, it will not help if the VM is deleted or the disk is damaged.

Restoring the configuration

The built-in restore process is available only during the initial setup of a new Portainer instance with an empty data store. You cannot upload a backup archive to an already configured instance.

To restore the configuration, stop and remove the current container:

docker stop portainer

docker rm portainer

Do not delete the old volume yet. Create a new empty volume: docker volume create portainer_restore

Start a temporary Portainer instance:

docker run -d \

–name portainer-restore \

–restart=always \

-p 127.0.0.1:9001:9000 \

-v /var/run/docker.sock:/var/run/docker.sock \

-v portainer_restore:/data \

portainer/portainer-ce:lts

To access it through the existing domain, temporarily change proxy_pass in the Nginx configuration: proxy_pass http://127.0.0.1:9001;

Check and apply the configuration:

sudo nginx -t

sudo systemctl reload nginx

Open the Portainer domain. On the initial setup page, expand: Restore Portainer from backup

Select the downloaded .tar.gz file, enter the password if the archive was protected, and start the restore. After it completes, the login page will open, and the previous users and settings will be restored.

Checking the VPS after a reboot

The final check is needed to make sure that Docker, Nginx, and Portainer start automatically with the server and that the panel remains accessible over HTTPS.

First, reboot the VPS: sudo reboot

The SSH connection will close automatically. Wait about a minute and reconnect: ssh -i .\portainer-guide.pem ubuntu@PUBLIC_IP

Check the Docker status:

systemctl is-active docker

systemctl is-enabled docker

Expected result:

active

enabled

Check Nginx:

systemctl is-active nginx

systemctl is-enabled nginx

The service should also be in the following states:

active

enabled

Check the Portainer container: docker ps –filter name=portainer

The STATUS column should show the Up state.

For compact output, run:

printf &quot;docker: &quot;

systemctl is-active docker

printf &quot;nginx: &quot;

systemctl is-active nginx

docker ps –filter name=portainer \

–format &quot;table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}&quot;

The output should show the active Docker and Nginx services, as well as the running portainer container.

Then open the following URL in your browser: https://portainer.example.com

Sign in to your account and make sure that:

  • The local Docker Environment has the status Up;
  • The test container is running;
  • The Docker Compose Stack is displayed in the list;
  • The network and volumes that were created have been preserved;
  • The panel opens without certificate warnings.

If Portainer did not start, check its restart policy:

docker inspect portainer \

–format ‘{{.HostConfig.RestartPolicy.Name}}’

Expected result: always

You can view the container log with the following command: docker logs –tail=100 portainer

Conclusion

Portainer simplifies day-to-day Docker management on a VPS and allows core operations to be performed through a web interface. Once installed, an administrator can start and restart containers, view logs, manage networks and volumes, and deploy applications as Docker Compose stacks.

In the configuration described, Portainer runs in a separate container and connects to the local Docker Engine through the Docker socket. The panel settings are stored in the named volume portainer_data, so the container can be recreated and updated without losing its configuration.

Access to the panel is configured through a dedicated domain, Nginx, and a Let’s Encrypt certificate. The Portainer port is not exposed directly to the internet: external HTTPS requests are handled by Nginx and forwarded to the panel over a local connection.

For ongoing operation, it is important to update Docker and Portainer regularly, keep backups of the panel configuration, and back up application data from Docker volumes separately. This approach allows Portainer to serve as a convenient and secure management point for the VPS container infrastructure.

FAQ

Can you use Portainer without a domain?

Yes. Portainer can be accessed directly via the VPS IP address and the published port, for example: https://PUBLIC_IP:9443

By default, Portainer uses HTTPS on port 9443 and generates a self-signed certificate. As a result, the browser may display a warning about an untrusted certificate. For ongoing use, it is more convenient to set up a domain and use a Let’s Encrypt certificate through Nginx.

Where Portainer Stores Its Settings

Portainer stores its database and configuration in the /data directory inside the container. In this guide, that directory is mounted from a named Docker volume: portainer_data:/data

This ensures that the settings are retained after the Portainer container is stopped, removed, or updated. The built-in backup is also created from the contents of /data.

How to Update Portainer Without Losing Data

Before updating, it is recommended to back up the configuration. Then stop and remove the old container, pull the new image, and start Portainer again using the same portainer_data volume.

Example:

docker stop portainer

docker rm portainer

docker pull portainer/portainer-ce:lts

After that, start the container again with the same volume mount: -v portainer_data:/data

As long as the volume has not been deleted, Portainer uses the saved configuration and updates its internal database when the new version starts.

Can Nginx also be run in a container?

Yes, this configuration is possible and supported. It can also be convenient when publishing applications over HTTPS, so you do not have to change ports and can use the standard port 443 instead. Our deployment setup with Nginx running on the host is provided as an example and to simplify configuration.

How to Secure Portainer

Exposing the admin interface to the entire internet is not very secure, even when using a domain name that an attacker would need to know. In addition to using a non-standard port in Nginx, you can use:

  1. IP restrictions via Nginx, a firewall on the VPS, or Security Groups in the cloud;
  2. Nginx authentication, which can be either an additional username/password or client certificate authentication;
  3. A VPN, preferably with two-factor authentication; alternatively, SSH tunneling;
  4. Fail2ban to protect against brute-force attacks.

And, of course, a strong password for the administrative account.

How a Stack Differs from a Regular Container

A regular container is a single application instance created from a Docker image. A Stack is deployed from a Docker Compose configuration and can include multiple related services, networks, volumes, ports, and environment variables at once.

For example, a single Stack can create a web application, a database, Redis, a shared network, and persistent volumes at the same time. All components of such an application are then managed as a single configuration.

Can I manage multiple VPS instances?

Yes. A single Portainer instance can manage multiple environments. You can connect additional Docker Standalone, Docker Swarm, Kubernetes, and other supported environments to it.

To connect remote VPS instances, Portainer Agent, Edge Agent, or a secure connection to the Docker API is typically used. Once added, the servers appear in the Environments, and access to them can be controlled for users and groups.

Sources

  1. Portainer Documentation — Install Portainer CE with Docker on Linux
  2. Portainer Documentation — Updating on Docker Standalone
  3. Portainer Documentation — General settings and backups
  4. Portainer Documentation — Add a new environment

Subscribe to our newsletter and receive articles and news

    Check out our other materials