Skip to content
+1 (813) 212-3723 support@layeronecloud.com
Guides

Running Docker

Install Docker Engine, and the one firewall trap that catches everybody.

3 min read Reviewed 24 Aug 2026

Install from Docker's apt repository

On Ubuntu or Debian, add Docker's signed repository. This keeps the repository and signing key visible in the package manager instead of piping a downloaded installation script into a root shell.

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings

. /etc/os-release
case "$ID" in
  ubuntu|debian) ;;
  *) echo "This guide supports Ubuntu and Debian" >&2; exit 1 ;;
esac

sudo curl -fsSL "https://download.docker.com/linux/$ID/gpg"   -o /etc/apt/keyrings/docker.asc
sudo 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"   | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io   docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"

Log out and back in for the group to apply, then:

docker run --rm hello-world
docker compose version

The firewall trap

Docker writes its own iptables rules and publishes ports past a firewall running inside the server. -p 5432:5432 on a database container is reachable from the internet even with ufw denying everything, and people are still surprised by this years after it was documented.

Two ways to avoid it:

Bind to localhost. Anything that only your own containers or a reverse proxy need to reach:

services:
  db:
    image: postgres:17
    ports:
      - "127.0.0.1:5432:5432"

Better still, publish no port at all and let containers talk over the compose network by service name.

Use the LayerOne cloud firewall. It filters upstream of the server, so it is not affected by what Docker does to iptables. Inbound DROP with explicit allows is the safe posture. See The cloud firewall.

Check what you have exposed

bash docker ps --format 'table {{.Names}}\t{{.Ports}}' sudo ss -tulpn | grep -v '127.0.0.1\|::1' Anything on 0.0.0.0 is on the internet.

Keeping the disk from filling up

Images and volumes accumulate quietly, and a full disk is one of the most common "my server broke" tickets.

docker system df          # what is using space
docker system prune -a    # remove unused images, containers, networks

prune -a does not touch named volumes. Add --volumes only when you are sure.

Cap log growth, which otherwise grows without limit:

sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
sudo systemctl restart docker

Memory

Containers see the whole host by default, so one runaway process can take the server down. On a small plan, set limits:

services:
  app:
    image: your/app
    deploy:
      resources:
        limits:
          memory: 512M
    restart: unless-stopped

If you are running several containers on a 1 GB plan, add swap:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Backups

Docker volumes are on the server disk, and the disk is gone when the server is. We take no backups. Copy volumes off the box:

docker run --rm -v myapp_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/myapp-data.tar.gz -C /data .

Then move that file somewhere else, on a schedule. See Backups and snapshots.

Still stuck

Chat with us from the portal.

Ask the assistant from the Chat bar. During business hours you can ask for a person and a human joins live.