Install Portainer on Ubuntu Server

Why Ubuntu for Portainer?

Ubuntu Server is where most people run Docker, and Portainer is how most people manage it without memorizing CLI commands. This guide covers the complete production setup: Docker CE installation, Portainer with proper socket permissions, HTTPS access with self-signed or Let’s Encrypt certificates, and Portainer Agent deployment for managing remote Docker hosts from a single dashboard.

Prerequisites

  • Ubuntu 22.04 or 24.04 LTS server
  • Docker and Docker Compose installed (guide)
  • 512 MB of free RAM
  • 1 GB of free disk space
  • SSH access with a sudo-capable user
  • Docker socket access (/var/run/docker.sock)

Install Docker CE

sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) 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-compose-plugin
sudo usermod -aG docker $USER

Log out and back in for the group change.

Docker Compose Configuration

Create the project directory:

mkdir -p ~/portainer && cd ~/portainer

Create docker-compose.yml:

services:
  portainer:
    image: portainer/portainer-ce:2.39.1
    container_name: portainer
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    volumes:
      # Docker socket — required for managing local Docker
      - /var/run/docker.sock:/var/run/docker.sock
      # Persistent data (users, settings, stacks)
      - portainer-data:/data
    ports:
      # HTTPS web UI (primary access method)
      - "9443:9443"
      # HTTP web UI (optional — disable in production)
      - "9000:9000"
      # TCP tunnel server for Edge Agents
      - "8000:8000"

volumes:
  portainer-data:

Start Portainer:

docker compose up -d

Verify:

docker compose ps
curl -sk https://localhost:9443/api/status | python3 -m json.tool

First-Time Setup

  1. Navigate to https://your-server-ip:9443
  2. Your browser will warn about the self-signed certificate — this is expected. Accept and continue.
  3. Create your admin account (set a strong password — there is no password reset mechanism)
  4. Select Get Started to connect Portainer to the local Docker environment
  5. The local Docker endpoint appears automatically because of the socket mount

Key Areas of the Portainer UI

  • Home: overview of all connected environments
  • Containers: list, start, stop, restart, inspect, and remove containers
  • Images: pull, tag, and remove Docker images
  • Volumes: create and manage volumes
  • Networks: view and create Docker networks
  • Stacks: deploy Docker Compose stacks directly from the UI
  • App Templates: one-click deployment templates (optional, community-maintained)

Docker Socket Permissions

The Docker socket (/var/run/docker.sock) gives full control over Docker. Portainer needs it to manage containers. Security considerations:

Default setup (root access). The Compose file above runs Portainer as root, which can access the socket without issues. This is the standard approach.

Restrict with no-new-privileges. The security_opt: - no-new-privileges:true line prevents the container from escalating privileges beyond what it started with.

Socket proxy (maximum security). For hardened environments, use a Docker socket proxy that exposes only the API endpoints Portainer needs:

services:
  docker-socket-proxy:
    image: tecnativa/docker-socket-proxy:0.3.0
    container_name: docker-socket-proxy
    restart: unless-stopped
    environment:
      - CONTAINERS=1
      - IMAGES=1
      - NETWORKS=1
      - VOLUMES=1
      - SERVICES=1
      - TASKS=1
      - INFO=1
      - POST=1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - portainer-internal

  portainer:
    image: portainer/portainer-ce:2.39.1
    container_name: portainer
    restart: unless-stopped
    command: -H tcp://docker-socket-proxy:2375
    volumes:
      - portainer-data:/data
    ports:
      - "9443:9443"
      - "9000:9000"
      - "8000:8000"
    networks:
      - portainer-internal

volumes:
  portainer-data:

networks:
  portainer-internal:

HTTPS Configuration

Self-Signed Certificate (Default)

Portainer generates a self-signed certificate automatically. Access at https://your-ip:9443. Browsers will show a warning — acceptable for internal use.

Let’s Encrypt via Reverse Proxy

For a proper certificate, put Portainer behind a reverse proxy:

  1. Set up Nginx Proxy Manager or Caddy (Reverse Proxy guide)
  2. Point your domain (e.g., docker.example.com) at the server
  3. Create a proxy host forwarding to https://portainer:9443 with WebSocket support enabled
  4. Request a Let’s Encrypt certificate

When behind a reverse proxy, you can remove the port mappings from the Compose file and only expose Portainer on the internal Docker network.

Portainer Agent for Remote Hosts

To manage Docker on other machines from a single Portainer instance, deploy the Portainer Agent on each remote host:

# On each remote Docker host
docker run -d \
  --name portainer-agent \
  --restart unless-stopped \
  -p 9001:9001 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /var/lib/docker/volumes:/var/lib/docker/volumes \
  portainer/agent:2.39.1

Then in Portainer:

  1. Go to Environments > Add environment
  2. Select Docker Standalone > Agent
  3. Enter the remote host IP and port 9001
  4. Name the environment
  5. Click Connect

You can now manage all your Docker hosts from the single Portainer dashboard.

Ubuntu-Specific Optimization

UFW firewall rules:

sudo ufw allow 9443/tcp comment "Portainer HTTPS"
# Only if needed:
sudo ufw allow 9000/tcp comment "Portainer HTTP"
sudo ufw allow 8000/tcp comment "Portainer Edge Agent"

Disable HTTP access in production. Remove the port 9000 mapping from your Compose file once you confirm HTTPS works.

Automatic security updates:

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Docker log rotation. Add to /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Troubleshooting

”Your Portainer instance timed out” on first access

You have a 5-minute window to create the admin account after starting Portainer. If you miss it:

docker compose restart portainer

Then immediately navigate to the web UI and create the account.

Cannot connect to Docker socket

Check socket permissions:

ls -la /var/run/docker.sock
# Should show: srw-rw---- 1 root docker

Ensure the Portainer container can access it. If running as non-root, add the user to the docker group.

Port 9443 already in use

Another service is binding to 9443. Change the host port:

    ports:
      - "19443:9443"

Portainer Agent connection refused

Verify the agent is running on the remote host:

docker ps | grep portainer-agent

Check that port 9001 is open on the remote host’s firewall and reachable from the Portainer server.

Stacks deployed via Portainer don’t persist after Portainer restart

Stacks deployed through the Portainer UI are managed by Portainer and stored in its data volume. If you recreate the Portainer container without preserving the portainer-data volume, stack metadata is lost (the containers themselves remain running).

Resource Requirements

  • RAM: ~100 MB idle, ~200 MB under active use
  • CPU: Low. Spikes briefly when loading the UI or deploying stacks.
  • Disk: ~200 MB for the image, plus data volume for settings and stack metadata

Comments