Deploying Portainer
High-level summery of why Portainer was chosen, deployment steps, compose file, permission fixes, and security notes for a homelab.
Why Portainer#
Portainer was chosen for these practical reasons:
- Administrative UI: a clean web interface for container lifecycle management (create, update, inspect), which reduces CLI friction as the number of containers grows.
- Operational hygiene: easier to maintain stacks, inspect volumes, networks and resource usage; useful when shifting from single-host experimentation to a managed homelab.
- Monitoring & alerts integration: Portainer can be a centralized place to view logs, and it can integrate with external alerting / webhook systems for security notifications and automation (use webhooks or external monitoring tools for alerts).
- Faster on-boarding: visual controls help when sharing access with collaborators or when returning to the environment after a break.
Originally the goal was minimal CLI-only stacks. That worked while the environment was tiny, but as container count grew the CLI-only workflow became restrictive. Portainer restores visibility and lowers operational overhead while still allowing scripted automation.
Deployment context#
- Service account for deployments:
iamdocker(member of thedockergroup) - Service stacks directory:
/opt/dockerstacks/(each stack in its own subfolder) - This stack:
/opt/dockerstacks/portainer - Network binding: Tailscale interface at
100.111.X.X(Portainer bound to this interface to minimize admin ui exposure)
Commands#
# switch to the deployment user
sudo -u iamdocker -s
cd /opt/dockerstacks/
mkdir -p /opt/dockerstacks/portainer
# create named volume for Portainer data
docker volume create portainer_data
# ensure volume data is owned by the host UID/GID we will run the container as (iamdocker)
sudo chown -R 111:222 /var/lib/docker/volumes/portainer_data/_databashNote: 111:222 corresponds to the host iamdocker UID and docker GID on this system. Adjust if UIDs/GIDs differ on other hosts.
Create docker-compose file#
services:
portainer:
container_name: portainer
image: portainer/portainer-ce:lts
restart: always
user: "111:222" # match host iamdocker UID:GID (optional — see notes)
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
ports:
- 100.111.X.X:13443:9443 # Tailscale interface (HTTPS)
security_opt:
- no-new-privileges:true
volumes:
portainer_data:
name: portainer_data
networks:
default:
name: portainer_networkyamlRecommended tweak: consider restart: unless-stopped instead of always to avoid automatic restarts after intentional stops.
Start Portainer container#
sudo -u iamdocker -s
cd /opt/dockerstacks/portainer
docker compose pull
docker compose up -dbashThe portainer admin ui should be acceeesible on Tailscal vpn network on port 13443.
Portainer gives the homelab improved visibility and operational hygiene while preserving scriptable deployments under /opt/dockerstacks. The current configuration balances convenience and security by restricting network exposure (Tailscale) and applying basic container hardening (no-new-privileges).