Start typing to search across invoices, services, domains, tickets, and more...
This guide shows how to install Docker and Docker Compose on a Linux server using Docker's official repository, with separate commands for Debian/Ubuntu and Rocky Linux/AlmaLinux. It also covers post-install setup, running your first container, registry mirrors and the classic pitfall of Docker publishing ports straight past your firewall. It applies to IMIDC VPS and dedicated servers running a 64-bit OS with root access.
Distribution packages such as docker.io or podman-docker conflict with Docker CE. On a fresh system the commands simply report that nothing is installed. Existing images and volumes in /var/lib/docker are not deleted by removing these packages.
# Debian / Ubuntu: remove distro packages that conflict with Docker CE
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do apt-get remove -y $pkg; done
# Rocky Linux / AlmaLinux
dnf remove -y docker docker-client docker-client-latest docker-common docker-latest \
docker-latest-logrotate docker-logrotate docker-engine podman runc
The official repo ships newer releases than the distro archives and includes the docker compose plugin (Compose V2). The commands read /etc/os-release to detect Debian or Ubuntu and the release codename, so you can paste them as-is.
apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
. /etc/os-release # sets $ID (debian/ubuntu) and $VERSION_CODENAME
curl -fsSL https://download.docker.com/linux/$ID/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$ID $VERSION_CODENAME stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
On RHEL-compatible systems use Docker's rhel repository, which works for Rocky Linux and AlmaLinux 8 and 9. During the first install dnf asks you to accept Docker's GPG key; check the fingerprint and answer y.
dnf install -y dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enable the service at boot and verify it with hello-world. To let a regular user run docker without sudo, add them to the docker group, but remember that membership grants root-equivalent rights, so only do this for trusted accounts.
systemctl enable --now docker
docker version
docker compose version
docker run --rm hello-world
# Optional: allow a normal user to run docker (this is equivalent to root access)
usermod -aG docker deploy
# log out and log in again as "deploy", then test:
docker ps
Compose V2 is invoked as docker compose (with a space) and the preferred file name is compose.yaml. The example below starts an Nginx container bound to 127.0.0.1 only; you can later publish it through a host Nginx reverse proxy with HTTPS (see our Nginx reverse proxy tutorial).
mkdir -p /opt/web && cd /opt/web
cat > compose.yaml <<'EOF'
services:
web:
image: nginx:stable
ports:
- "127.0.0.1:8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
EOF
mkdir -p html && echo "Hello from Docker" > html/index.html
docker compose up -d
docker compose ps
curl http://127.0.0.1:8080
docker compose logs -f web # Ctrl+C to stop following
IMIDC servers in Hong Kong, Japan, Korea and other regions normally reach Docker Hub directly, so a mirror is rarely needed. If pulls are slow, add a mirror you trust in daemon.json (registry-mirrors applies to Docker Hub only). It is also wise to cap container log size so logs cannot fill the disk. Anonymous pulls are rate limited, so run docker login if you pull often.
mkdir -p /etc/docker
cat > /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": ["https://mirror.example.com"],
"log-driver": "json-file",
"log-opts": { "max-size": "20m", "max-file": "3" }
}
EOF
systemctl restart docker
docker info | grep -A2 "Registry Mirrors"
# Avoid anonymous Docker Hub pull limits
docker login
journalctl -u docker -n 50 if the restart fails.Docker writes its own iptables/nftables rules to publish ports, and those rules are evaluated before ufw's. As a result a port published with -p 8080:80 is reachable from the internet even if ufw never allowed 8080. The safest pattern is to bind to 127.0.0.1 and front the app with Nginx; if a port must be public, filter sources in the DOCKER-USER chain.
# 1) Recommended: publish only on localhost and expose the app through Nginx
# compose.yaml -> ports: - "127.0.0.1:8080:80"
# 2) Restrict a published port in the DOCKER-USER chain
# (only 198.51.100.20 may reach container port 8080 via eth0)
iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL \
! -s 198.51.100.20 -j DROP
iptables -L DOCKER-USER -n --line-numbers
# 3) firewalld (Rocky/AlmaLinux): Docker adds its own "docker" zone
firewall-cmd --get-active-zones
firewall-cmd --zone=docker --list-all
# After reloading or restarting the firewall, restart Docker to rebuild its rules
systemctl restart docker
-p 3306:3306 to the internet. It is one of the most common causes of breaches and ransom attacks.Your user is not in the docker group, or you have not logged in again since adding it. Run id to check, reconnect over SSH, or simply use root or sudo.
docker-compose (with a hyphen) is the retired V1 standalone tool. docker compose is the V2 plugin installed with docker-compose-plugin. The syntax is largely compatible, so use V2 for all new projects.
Reloading firewalld or flushing iptables removes the NAT rules Docker created. Run systemctl restart docker and Docker recreates them.
Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, OS version, the commands you ran and the full error output so we can pinpoint the issue faster.