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:
- Docker Engine runs Portainer and the other containers.
- Portainer connects to the local Docker Engine through the /var/run/docker.sock socket.
- Portainer accepts requests only on the VPS’s local interface.
- Nginx accepts external requests for the domain and forwards them to Portainer.
- Certbot issues an SSL certificate for the domain.
- 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 "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
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 "upgrade";
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 "Host: portainer.example.com" 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:
- Provide an email address;
- Accept the terms of service;
- Choose whether to subscribe to the mailing list;
- 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:
– "127.0.0.1:8081:80"
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: "yes"
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 "<h1>Portainer Volume Demo</h1>" > /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 "docker: "
systemctl is-active docker
printf "nginx: "
systemctl is-active nginx
docker ps –filter name=portainer \
–format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
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:
- IP restrictions via Nginx, a firewall on the VPS, or Security Groups in the cloud;
- Nginx authentication, which can be either an additional username/password or client certificate authentication;
- A VPN, preferably with two-factor authentication; alternatively, SSH tunneling;
- 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.
