Install Docker Portainer on AlmaLinux 8 and manage your containers through a private HTTPS dashboard in about ten minutes. You will install Docker Engine from its official RPM repository, keep Portainer data persistent, and reach the UI through an SSH tunnel instead of exposing its management port to the internet.
Prerequisites
- A 64-bit AlmaLinux 8 VPS with at least 2 GB of RAM and 10 GB of free disk space for images and container data.
- SSH access as
root, or a regular account with workingsudoprivileges. - Outbound HTTPS access to Docker Hub and Docker’s RPM repository.
- An SSH client on your local computer. This guide does not require a domain because Portainer stays on the VPS loopback address.
- A VPSLake Linux VPS with root access if you need a server for this installation.
The commands use sudo for a non-root account. If you are already logged in as root, remove sudo from each command.
Step 1: Confirm AlmaLinux 8 and update the host
Confirming the release first prevents you from applying instructions meant for Ubuntu or a different Enterprise Linux release.
grep -E '^(PRETTY_NAME|VERSION_ID|ID_LIKE)=' /etc/os-release
uname -m
sudo dnf -y upgrade
The release should show AlmaLinux 8 and the architecture should normally be x86_64:
PRETTY_NAME="AlmaLinux 8.x (Cerulean Leopard)"
VERSION_ID="8.x"
ID_LIKE="rhel centos fedora"
x86_64
The minor version and kernel packages can change as AlmaLinux publishes updates. If DNF reports repository, DNS, or signature errors, fix those before installing Docker.
Step 2: Add Docker’s repository
Docker’s RPM repository supplies a compatible Engine, containerd, Buildx, and Compose plugin set. Docker documents the RHEL repository method in its official RHEL installation guide.
On a new VPS, remove packages that commonly conflict with Docker’s runtime, then add the repository:
sudo dnf remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine \
podman \
runc
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
DNF may say that some packages were not installed; that is expected on a fresh machine. Do not remove Podman or runc from an established host until you have checked which workloads depend on them. Docker data under /var/lib/docker/ is not deleted by package removal.
Check that the repository exposes Docker packages before continuing:
dnf list docker-ce --showduplicates | tail -n 5
You should see one or more docker-ce entries from Docker’s repository. The exact version changes over time.
Step 3: Install and start Docker Engine
Installing the Compose plugin at the same time gives you the standard docker compose command for future stacks managed through Portainer.
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
sudo docker compose version
Expected output has matching client and server versions, followed by a Compose version:
Client 2x.x.y / Server 2x.x.y
Docker Compose version v2.x.y
Run Docker’s disposable test container. It verifies the daemon, registry access, and container runtime without leaving a stopped test container behind:
sudo docker run --rm hello-world
The final lines should include Hello from Docker! and Your installation appears to be working correctly.
Step 4: Keep Portainer private on AlmaLinux 8
Portainer’s web UI uses HTTPS on port 9443, but binding it to loopback means only local processes—and an SSH tunnel you create—can reach it. This also avoids adding an inbound 9443 rule to firewalld.
Check the host security state before deploying the container:
getenforce
sudo firewall-cmd --state
Typical output is:
Enforcing
running
Leave SELinux enabled. If firewalld is active, it does not need a Portainer rule because the service will listen only on 127.0.0.1; do not open 9443 publicly for this setup.
Step 5: Deploy Portainer CE with persistent storage
The named volume stores Portainer users, settings, and environment details outside the container, so recreating the container does not reset the dashboard. Portainer’s official Docker installation guide documents the Docker socket and data-volume arrangement used here.
On an SELinux-enabled host, --privileged is required for Portainer to manage the local Docker environment through the mounted Unix socket. The UI remains private because the host-side port is explicitly bound to loopback.
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 container ID. Confirm the container is running and that its 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
Step 6: Open the dashboard through SSH
An SSH local-forward carries your browser connection to the private listener without changing the VPS firewall.
Run this on your local computer, not inside the VPS shell. Replace the uppercase values and keep this terminal open:
ssh -N -L 9443:127.0.0.1:9443 YOUR_SSH_USER@SERVER_IP
Browse to https://127.0.0.1:9443. Portainer uses a self-signed certificate on first launch, so your browser will show a certificate warning for this private connection. Continue to the page, create a strong unique administrator password, and select the local Docker environment.
If port 9443 is already occupied 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-side service remains on 9443.
Verify Docker and Portainer
Run these checks on the VPS to confirm Docker starts at boot, the container has its restart policy, and the HTTPS endpoint answers locally:
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 --head --max-time 10 https://127.0.0.1:9443
Expected results include:
active
enabled
always
LISTEN ... 127.0.0.1:9443 ...
HTTP/1.1 200 OK
The Portainer page should load through your SSH tunnel, and the local Docker environment should appear after setup.
Troubleshooting
No match for argument: docker-ce
DNF cannot see Docker’s repository, usually because the repository helper was not installed or the repository metadata is stale. Refresh and inspect the enabled list:
sudo dnf repolist --enabled | grep -i docker
sudo dnf makecache
dnf list docker-ce --showduplicates
If no Docker repository appears, repeat the dnf config-manager --add-repo command in Step 2 and confirm that the VPS can resolve download.docker.com over HTTPS.
package podman ... conflicts with ...
AlmaLinux may have Podman or a standalone runtime installed already. On a new VPS, rerun the removal command from Step 2; on an active host, keep the existing runtime until you have planned a migration because Docker and Podman should not be changed casually under running workloads.
Portainer exits immediately or Docker reports an SELinux denial
Portainer needs the Docker socket, and SELinux can block the required container access. Confirm the host is enforcing SELinux, inspect the recent log, and recreate the container with the --privileged flag shown in Step 5:
getenforce
sudo docker logs --tail 80 portainer
Do not solve this by disabling SELinux. The documented privileged container setting keeps the host policy enabled while providing Portainer the access it needs.
ssh: connect to host ... Connection refused or a blank browser page
The tunnel command must run on your workstation and its local port must match the URL you open. Start it again, leave that terminal running, then check the VPS listener:
sudo docker ps --filter name=portainer
sudo ss -lntp | grep ':9443'
The port listing should show 127.0.0.1:9443, not a public address. If the container is stopped, read sudo docker logs portainer before recreating it.
Hardening
- Keep the Portainer socket and dashboard on loopback. If you later publish it through a reverse proxy, use a real certificate, restrict firewalld to trusted sources, and protect the hostname with strong authentication.
- Treat Portainer administrator access and membership in the
dockergroup as root-equivalent. This guide keeps Docker commands undersudoinstead of broadening access for every local user. - Back up the
portainer_datavolume before upgrades and keep Docker images on a disk with enough free space. - Use VPSLake’s SSH hardening generator to review SSH settings before relying on a tunnel for administration.
FAQ
Does AlmaLinux 8 use dnf or yum for this installation?
Use dnf. AlmaLinux 8 supports the DNF command and the Docker documentation’s RHEL repository method, so there is no reason to substitute older yum instructions.
Why does Portainer need the Docker socket?
The socket is how Portainer sends management requests to the local Docker Engine. Mounting it gives the dashboard control over containers, images, networks, and volumes, so only trusted administrators should access Portainer.
Do I need Portainer’s port 8000?
No. Port 8000 is used for Portainer Edge Agent tunnel features. A single VPS managed through its local Docker socket can leave that port unpublished.
How can I upgrade Portainer without losing the dashboard?
Keep the portainer_data volume and replace only the container with the newer portainer/portainer-ce:lts image. Back up the volume first, and never remove it as part of a routine image upgrade because it contains the Portainer database and configuration.