How to Install Docker Portainer on AlmaLinux 10

Install Docker Portainer on AlmaLinux 10 with Docker Engine, Portainer CE, private HTTPS access, persistent data, and verification steps for a fresh VPS.
A polished 3D server rack with glowing containers and a dashboard panel on a midnight-blue background.

Install Docker Portainer on AlmaLinux 10 and manage your containers from a private HTTPS dashboard in about ten minutes. You will finish with Docker Engine, Portainer CE, persistent configuration, and no public management port.

Prerequisites

  • A 64-bit AlmaLinux 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 Linux account with working sudo privileges.
  • Outbound HTTPS access to Docker’s RPM repository and Docker Hub.
  • An SSH client on your workstation for a local tunnel. You do not need a domain for this private setup; a domain is required later if you put Portainer behind a public reverse proxy.
  • A VPSLake Linux VPS if you need a root-access AlmaLinux server for Docker workloads.

The commands use sudo for a regular SSH user. If you are logged in as root, remove sudo. AlmaLinux 10 uses DNF and firewalld, so do not replace these commands with Ubuntu’s apt or ufw workflow.

Step 1: Confirm AlmaLinux 10 and update the server

Checking the release before adding a repository keeps packages aligned with the Enterprise Linux 10 base.

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

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

PRETTY_NAME="AlmaLinux 10.x (Cerulean Lion)"
VERSION_ID="10.x"
ID_LIKE="rhel centos fedora"
x86_64

The minor version changes as AlmaLinux publishes updates. Resolve DNS, mirror, or signature errors before continuing.

Step 2: Remove packages that conflict with Docker Engine

The official Docker packages include their own containerd and runtime integration, so older distribution packages can create dependency conflicts.

On a fresh VPS, remove the common conflicting packages:

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

DNF may report that some packages are not installed; that is normal. Do not remove Podman or a runtime from an active production host until you know which services depend on it. Removing packages does not automatically erase Docker data in /var/lib/docker/.

Step 3: Add Docker’s official RPM repository

Docker’s RHEL repository provides the Engine, CLI, containerd, Buildx, and Compose plugin as a matched package set. Docker documents this RHEL 10 method in its official RHEL installation guide.

Install DNF’s repository helper and add the RHEL-compatible repository:

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

Check that DNF can see Docker before installing it:

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

You should see one or more docker-ce packages from Docker’s repository. The available version changes as Docker releases updates.

Step 4: Install Docker and enable the daemon

Installing the Compose plugin now gives you both docker compose and the Docker CLI needed by Portainer.

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

Confirm that systemd started Docker and that the client can reach the server:

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 have an active, enabled service and version values that may differ from these examples:

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

Run Docker’s test image to verify the daemon, registry connection, and container runtime:

sudo docker run --rm hello-world

Near the end, Hello from Docker! and Your installation appears to be working correctly. confirm that the container ran successfully. The stopped test container is deleted by --rm.

Step 5: Let one trusted account use Docker

Docker’s Unix socket is root-owned by default. Adding one administration account to the docker group avoids repeated sudo prompts, but the group grants root-equivalent control over the host.

Run this as the regular sudo-enabled SSH user who will manage Portainer:

sudo groupadd --force docker
sudo usermod -aG docker "$USER"

Close the SSH connection completely and reconnect so the new group is loaded. Then test the socket without sudo:

id -nG
docker info --format 'Server {{.ServerVersion}}'

The group list should include docker, and the second command should print the Docker Engine version. Keep this group limited to trusted administrators.

Step 6: Deploy Portainer CE with private HTTPS

The named volume preserves Portainer users, settings, and environment data when the container is recreated. Portainer’s official Docker installation guide uses the same socket mount, data volume, and HTTPS port.

Portainer supports SELinux-aware deployments with the --privileged flag. This command leaves SELinux enabled while granting the Portainer container the access it needs to manage the Docker socket. It binds port 9443 only to the VPS loopback address, and it omits port 8000 because Edge Agent features are not part of this single-host setup.

docker volume create portainer_data
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. Check that the container is running and that the listener is private:

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 creates a self-signed certificate for its first HTTPS connection, so a browser warning is expected during initial setup.

Step 7: Open the dashboard through an SSH tunnel

Because Portainer listens on loopback, the dashboard is not reachable directly from the internet. An SSH tunnel lets your workstation use the private listener without adding a firewalld exception.

Run this command on your local workstation, not in the VPS shell. Replace the uppercase placeholders and leave the terminal open:

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

Open https://127.0.0.1:9443 in your browser. Accept the self-signed certificate warning for this private tunnel, create a strong unique administrator password, and complete the initial Portainer setup.

If port 9443 is already used on your workstation, change the local side only:

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

Then visit https://127.0.0.1:19443. The VPS still uses its private port 9443, and firewalld does not need a new public rule.

Verify Docker and Portainer

Run these checks on the VPS to prove Docker starts at boot, Portainer is running, the restart policy is set, and the private HTTPS endpoint answers:

systemctl is-active docker
systemctl is-enabled docker
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' portainer
curl --insecure --head --max-time 10 https://127.0.0.1:9443
active
enabled
always
HTTP/1.1 200 OK

Refresh the browser tab through the SSH tunnel. You should see the Portainer setup or dashboard page and the local Docker environment.

Troubleshooting

No match for argument: docker-ce

DNF has not loaded Docker’s repository, or the repository file was added incorrectly. Inspect the enabled repositories and refresh metadata:

sudo dnf repolist --enabled | grep -i docker
sudo dnf makecache
dnf list --showduplicates docker-ce

The enabled list should contain Docker’s repository. If it is missing, repeat the repository command from Step 3 and ensure the server can resolve download.docker.com over HTTPS.

package podman ... conflicts with ...

An installed Podman or standalone runc package is still providing files that Docker expects to manage. On a new server, repeat Step 2 and then retry the Docker installation. On a host already running containers, choose one runtime and plan a migration before removing packages.

permission denied while trying to connect to the Docker daemon socket

Your current SSH session started before the account joined the docker group, or you modified a different account. Log out, reconnect as the intended user, and confirm that docker appears in id -nG; sudo docker info is only a temporary diagnostic.

The browser cannot connect to 127.0.0.1:9443

The private binding requires an active tunnel from your workstation. Start it again and keep that terminal open:

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

If the tunnel is running, inspect the container on the VPS:

docker ps --filter name=portainer
docker logs --tail 50 portainer

The port column should show 127.0.0.1:9443->9443/tcp. Do not browse to https://SERVER_IP:9443; this guide deliberately does not publish Portainer publicly.

Hardening

  • Keep Portainer on loopback unless you have a specific reason to expose it. For team access, use a domain, a reverse proxy with a trusted certificate, authentication controls, and a narrowly scoped firewalld rule.
  • Keep SELinux enforcing. The Portainer container uses --privileged here because its Docker-socket integration needs that access on an SELinux-enabled AlmaLinux host; do not weaken the host by disabling SELinux.
  • Treat the Docker socket, the docker group, and Portainer administrator access as root-equivalent. Remove old SSH keys and group memberships, and consider using VPSLake’s SSH hardening generator for a baseline SSH configuration.
  • Back up the portainer_data volume before upgrades. Never expose Docker’s unauthenticated TCP API on port 2375.

FAQ

Do I need to open port 9443 in firewalld?

No. Portainer is bound to 127.0.0.1, so firewalld does not need an inbound rule for it. The SSH connection carries your browser traffic to the local listener.

Why does the Portainer container use --privileged?

Portainer needs broad access to the Docker socket, and its Linux installation guidance calls for privileged mode when SELinux is enabled. The setup keeps that access limited by binding the HTTPS interface to loopback and leaving the host’s SELinux policy enabled.

What is Portainer port 8000 used for?

Port 8000 is the optional TCP tunnel used by Portainer Edge Agents. A Portainer server managing the Docker Engine on the same host through /var/run/docker.sock does not need it, so leaving it unpublished reduces the exposed surface.

How do I upgrade Portainer without losing its settings?

Keep the portainer_data volume and recreate only the container with the newer LTS image. Do not delete the volume: it contains the Portainer database, users, and environment configuration.

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

Windows Server 2019 vs 2022 for RDP: Which Should You Pick?

Next Article

Windows 11 on a VPS: TPM, Secure Boot and What Works

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 ✨