How to Install Docker Portainer on CentOS Stream 10

Install docker portainer on centos stream 10 with Docker Engine, persistent Portainer data, private HTTPS access, and VPS troubleshooting on fresh servers.
A polished 3D server rack connected to glowing containers and a secure dashboard on a midnight-blue background.

Install docker portainer on centos stream 10 and manage local containers from a private HTTPS dashboard in about ten minutes. You will use Docker’s official RPM repository, keep Portainer’s database in a named volume, and reach the interface through an SSH tunnel instead of exposing its management port publicly.

Prerequisites

  • A 64-bit CentOS Stream 10 VPS with at least 2 GB of RAM and 10 GB of free disk space for images, containers, and Portainer data.
  • SSH access as root, or a regular account with working sudo privileges.
  • Outbound HTTPS access to Docker’s package repository and Docker Hub.
  • An SSH client on your local computer. A domain is not needed for the private tunnel used here.
  • A VPSLake Linux VPS if you need a root-access server for this Docker deployment.

These commands use sudo for a non-root account. If you are logged in as root, remove sudo from the commands.

Step 1: Confirm CentOS Stream 10 and update the VPS

Checking the release and architecture first prevents a CentOS Stream repository from being applied to the wrong operating system.

grep -E '^(PRETTY_NAME|VERSION_ID|PLATFORM_ID)=' /etc/os-release
uname -m
sudo dnf -y upgrade

The first command should identify CentOS Stream 10, and the architecture should normally be x86_64:

PRETTY_NAME="CentOS Stream 10"
VERSION_ID="10"
PLATFORM_ID="platform:el10"
x86_64

Minor package and kernel versions change as the Stream repositories move forward. If DNF reports a DNS, mirror, or signature error, fix that before adding Docker’s repository.

Step 2: Remove old Docker packages and add Docker’s repository

Docker’s official packages can conflict with older distribution-provided Docker packages, so remove only the old Docker package names before installing the supported RPM set.

On a new VPS, run:

sudo dnf remove -y docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine

DNF may report that some packages are not installed; that is normal. Removing these packages does not automatically erase images, containers, volumes, or networks in /var/lib/docker/. Do not remove a runtime from a production host until you know which services depend on it.

Install the repository helper and register Docker’s CentOS repository:

sudo dnf install -y dnf-plugins-core ca-certificates
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf makecache --refresh

Docker’s CentOS installation guide lists CentOS Stream 10 as a maintained release and uses this repository URL. Confirm that DNF can see the packages before installing them:

dnf list --showduplicates docker-ce | tail -n 5

You should see docker-ce entries from docker-ce-stable. The version and .el10 package suffix can change as Docker publishes updates.

Step 3: Install and test Docker Engine

Installing the Compose plugin with Docker gives Portainer a complete Engine host and leaves the standard docker compose command available for later stacks.

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo systemctl is-active docker
sudo systemctl is-enabled docker
sudo docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
sudo docker compose version

Expected output will show an active, enabled service and version values that may differ from this example:

active
enabled
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y

Run Docker’s disposable test image. It checks daemon access, registry connectivity, and the container runtime, then removes the test container:

sudo docker run --rm hello-world

The final output should contain Hello from Docker! and Your installation appears to be working correctly.

Step 4: Check firewalld and SELinux before deploying Portainer

CentOS Stream 10 normally uses firewalld and SELinux; this setup keeps both enabled and avoids creating a public firewall exception for the dashboard.

getenforce
sudo systemctl is-active firewalld
sudo firewall-cmd --get-active-zones

Typical output is similar to:

Enforcing
active
public
  interfaces: eth0

The Portainer container will bind its host port to 127.0.0.1, so do not add 9443 or 8000 to a public firewalld zone. Keep your existing SSH rule in place because the tunnel in the next step uses SSH.

Step 5: Deploy Portainer CE with private HTTPS

The named volume stores Portainer users, settings, and environment data outside the container, so replacing the container does not reset the dashboard.

Portainer’s official Docker installation instructions use port 9443 for the HTTPS interface and make port 8000 optional for Edge Agent tunnel features. The command below omits 8000, binds 9443 only to loopback, and uses --privileged because Portainer’s documentation requires it when SELinux is enabled.

sudo docker volume create portainer_data
sudo docker run -d \
  --name portainer \
  --restart=always \
  --privileged \
  --publish 127.0.0.1:9443:9443 \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume portainer_data:/data \
  portainer/portainer-ce:lts

Docker prints a long container ID. Confirm that Portainer is running and that the published address is private:

sudo docker ps --filter name=portainer --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES      IMAGE                       STATUS       PORTS
portainer  portainer/portainer-ce:lts Up ...        127.0.0.1:9443->9443/tcp

Portainer generates a self-signed certificate for its initial HTTPS service. That warning is expected for the local tunnel; use a real certificate and a reverse proxy only if you later make the dashboard public.

Step 6: Open Portainer through an SSH tunnel

The SSH local forward carries browser traffic to the loopback-only listener without opening Portainer in firewalld or on the VPS’s public interface.

Run this command on your local computer, not inside the VPS shell. Replace YOUR_SSH_USER with your SSH account and SERVER_IP with the VPS address, then keep this terminal open:

ssh -N -L 9443:127.0.0.1:9443 YOUR_SSH_USER@SERVER_IP

Open https://127.0.0.1:9443 in your browser. Continue past the self-signed certificate warning, create a unique administrator password of at least 12 characters, and select the automatically detected local Docker environment.

If port 9443 is already being used on your computer, change only the local port:

ssh -N -L 19443:127.0.0.1:9443 YOUR_SSH_USER@SERVER_IP

Use https://127.0.0.1:19443 in that case. The VPS still listens privately on 9443.

Verify Docker and Portainer

These checks prove that Docker starts at boot, Portainer has its restart policy, the service is not listening publicly, and the HTTPS endpoint answers on the VPS:

sudo systemctl is-active docker
sudo systemctl is-enabled docker
sudo docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' portainer
sudo ss -lntp | grep ':9443'
curl --insecure --silent --show-error --output /dev/null --write-out '%{http_code}\n' https://127.0.0.1:9443

Expected results include:

active
enabled
always
LISTEN ... 127.0.0.1:9443 ...
200

Refresh the browser through the tunnel. You should see Portainer’s setup or dashboard page, and the local Docker environment should be available.

Troubleshooting

No match for argument: docker-ce

DNF cannot see Docker’s repository, usually because the repository helper was missing, the repo file was not added, or metadata is stale. Inspect the enabled repositories and rebuild the cache:

sudo dnf repolist --enabled
sudo dnf makecache --refresh
dnf list --showduplicates docker-ce

If no docker-ce-stable entries appear, repeat the repository command in Step 2 and confirm that the VPS can resolve download.docker.com over HTTPS.

permission denied while trying to connect to the Docker daemon socket

Docker’s Unix socket is root-owned by default, and this guide deliberately keeps commands under sudo instead of granting every login user root-equivalent Docker access. Run the affected command with sudo; if you later add a trusted account to the docker group, reconnect the SSH session before testing it.

Portainer is restarting or logs an SELinux denial

Portainer needs the Docker socket, and SELinux can block the required container access unless the container is privileged. Check the host mode and recent logs:

getenforce
sudo docker logs --tail 100 portainer
sudo docker inspect --format '{{.HostConfig.Privileged}}' portainer

The last command should print true. If it prints false, remove only the Portainer container and redeploy it with the exact --privileged, loopback binding, socket mount, and portainer_data volume from Step 5; the named volume preserves the stored configuration.

bind: address already in use

Another process is already using port 9443 on the VPS, so Docker cannot create Portainer’s listener. Identify the process, or use an unused loopback port such as 19443:

sudo ss -lntp | grep ':9443'

If you choose 19443, publish 127.0.0.1:19443:9443 in the container command, forward 19443:127.0.0.1:19443 over SSH, and browse to https://127.0.0.1:19443.

Hardening

  • Keep Portainer on loopback. If several administrators need access, put it behind a reverse proxy with a trusted TLS certificate, strong authentication, and a firewalld rule limited to the required HTTPS traffic.
  • Treat the Docker socket, the Portainer administrator account, and membership in the docker group as root-equivalent access. Limit them to trusted operators.
  • Back up the portainer_data volume before changing the image tag, and monitor free space under Docker’s data directories.
  • Review your SSH exposure with VPSLake’s SSH hardening generator before relying on the tunnel for administration.

FAQ

Does CentOS Stream 10 use dnf or yum for Docker?

Use dnf. CentOS Stream 10 is an Enterprise Linux 10 release, and Docker’s current CentOS instructions use DNF’s repository plugin and the CentOS RPM repository.

Why is Portainer not available at https://SERVER_IP:9443?

That is intentional: the host-side binding is 127.0.0.1:9443, which prevents direct public access. The SSH tunnel makes the service available only to the workstation that opened the tunnel.

Is port 8000 required for a single Docker VPS?

No. Port 8000 is Portainer’s optional TCP tunnel for Edge Agent features. A local Portainer Server managing the Docker Engine through /var/run/docker.sock can leave it unpublished.

Can I update Portainer without losing containers and users?

Yes, provided you keep the portainer_data volume and do not delete Docker’s application data. Replace the container during a planned upgrade, then reuse the same volume and socket mount; your Portainer database remains separate from the container image.

Samrat Ghosh
Written by

Samrat Ghosh

Founder & Infrastructure Engineer

Samrat Ghosh is the founder of VPSLake, where he builds and runs the remote desktop and VPS hosting infrastructure the company is built on. He writes hands-on guides about RDP, Windows Server, VPS management and secure remote access - the practical documentation he wishes had…

Previous Article

Remote Desktop Mac: Use Microsoft's Windows App

Next Article

How to Check RDP Login History (4624 and 4625)

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨