Skip to main content

Bundled visual tools — Portainer & pgAdmin

The Full Docker layout and Swarm layout ship with two visual ops tools pre-wired behind Traefik:

ToolPathWhat it's for
Portainer CE/portainerContainer-level UI — see every service, tail logs, restart, exec a shell, browse volumes.
pgAdmin/pgadminPostgres GUI — browse schemas, run SQL, inspect query plans, manage roles.

No separate install, no extra compose file. Bring up the Full or Swarm stack and both tools are live on the same host name as Liberty itself.

Not in the Light layout

The Light layout is a single container with a SQLite DB — no Postgres, no Traefik. Portainer and pgAdmin only ship with the Full and Swarm layouts. If you started on Light and want these tools, move to Full.


What you get out of the box

Open the host in a browser and Traefik routes the three paths:

PathBehind itFirst-run state
/ (catchall)liberty-nextSign in with admin + LIBERTY_ADMIN_PASSWORD from .env.
/portainerPortainer CEEmpty — first browser visit becomes the admin-setup wizard.
/pgadminpgAdmin 4Sign in with admin@example.com + PGADMIN_PASSWORD from .env.

install.sh full generates random secrets for both passwords on first run and stores them in .env mode 0600. Read them once with grep -E '^(PGADMIN_PASSWORD|LIBERTY_ADMIN_PASSWORD)=' .env.


Portainer — first run

  1. Open http://<host>/portainer (or https:// once TLS is wired).
  2. The page is the initial admin-setup wizard — Portainer treats the first visitor as the bootstrap operator. Create the admin account: username (often admin) + a 12+ character password.
  3. The local Docker endpoint is auto-detected (the compose mounts the socket — see below). No need to add an environment.
  4. Skip the licensing prompt — Community Edition is free for self-hosted use.

After the wizard, the dashboard lists every container running on the host. The Liberty stack shows up as the liberty Compose project (or liberty Swarm stack).

The first visit IS the admin setup

If the URL sits exposed before someone you trust visits it, the first stranger to reach it claims the admin account. Open the URL yourself right after install.sh full finishes, or block public access at the firewall until you've completed the wizard.


The Docker socket mount — what makes Portainer work

Portainer needs to talk to the Docker daemon to inspect and manage containers. The bundled service does this with a bind-mount of the host socket:

docker-compose.full.yml (portainer service)
portainer:
image: portainer/portainer-ce:latest
restart: unless-stopped
command: -H unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock # privileged surface
- portainer-data:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=PathPrefix(`/portainer`)"
- "traefik.http.routers.portainer.priority=100"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
- "traefik.http.middlewares.portainer-strip.stripprefix.prefixes=/portainer"
- "traefik.http.routers.portainer.middlewares=portainer-strip"

The Docker socket is a privileged surface: anything that can read it has root-equivalent control over every container on the host, including the ability to read environment variables (master key, JWT secret, Postgres password) from a running Liberty container.

ImplicationMitigation
A Portainer admin = root over every container on the host.Pick a strong admin password during the wizard; rotate it via My account → Change password.
Anyone who can reach /portainer over the network can attempt to claim it.Restrict the path at the edge (firewall, Traefik basic-auth middleware, IP allow-list) until TLS + a real admin exists.
You may not want this surface in the stack at all.Delete the portainer: service block plus the portainer-data: volume from docker-compose.full.yml and docker compose up -d. The rest of the stack is unaffected.

The portainer-data named volume holds Portainer's own DB — admin account, settings, endpoint definitions. backup.sh snapshots it.


What you can do from the bundled Portainer

Click Containers in the left nav. The Full layout shows five services:

ServiceAt-a-glance
liberty-nextStatus, ports, restart count, image tag.
pgPostgres health, volume size, port.
pgadminStatus — and yes, you can manage pgAdmin from Portainer (or vice versa).
portainerItself — meta-management.
traefikEdge router status + the live dashboard link.

Common operations:

TaskWhere in PortainerCLI equivalent
Tail logs from any serviceContainers → serviceLogs (auto-refresh, filter by level)docker compose logs -f <service>
Restart a serviceContainers → serviceRestartdocker compose restart <service>
Recreate a service (pull new image first)Containers → serviceRecreate → tick Pull latest imagedocker compose pull <service> && docker compose up -d <service>
Open a shell inside a containerContainers → serviceConsoleConnectdocker compose exec <service> bash
Inspect environment variables (read-only)Containers → serviceInspect tab → Config.Envdocker inspect <container>
Check resource usage (CPU / RAM)Containers → serviceStatsdocker stats
Browse / inspect a volumeVolumes → volumeBrowsedocker run --rm -it -v <vol>:/data alpine sh

Environment variables shown in Portainer are read-only — changing them needs an edit to .env followed by a recreate. The values displayed include secrets (master key, JWT secret); treat the Portainer admin login accordingly.


pgAdmin — first run

Same single URL story: open http://<host>/pgadmin and sign in with:

FieldValue
Emailadmin@example.com (the default user provisioned by the compose)
PasswordPGADMIN_PASSWORD from .env

The compose pre-wires two things that make the path-prefixed setup work:

docker-compose.full.yml (pgadmin service excerpt)
pgadmin:
image: dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: "${PGADMIN_PASSWORD:?PGADMIN_PASSWORD is required}"
SCRIPT_NAME: /pgadmin # tells pgAdmin its links live under /pgadmin
volumes:
- pgadmin-data:/var/lib/pgadmin
labels:
- "traefik.enable=true"
- "traefik.http.routers.pgadmin.rule=PathPrefix(`/pgadmin`)"
- "traefik.http.routers.pgadmin.priority=100"
- "traefik.http.services.pgadmin.loadbalancer.server.port=80"

SCRIPT_NAME=/pgadmin is what stops pgAdmin from generating absolute links to /login, /static/... (which would 404 because Traefik routes /pgadmin/* here, not /). Don't change it without also updating the Traefik router rule.

The pgadmin-data volume holds the saved server registrations, query history and preferences. backup.sh snapshots it.

Register the bundled Postgres

First login lands you on an empty dashboard. Right-click Servers → Register → Server…:

FieldValue
Name (General tab)liberty (anything you like)
Host (Connection tab)pg (the compose service name — the two containers share the default network)
Port5432
Usernamepostgres (or whatever POSTGRES_USER resolves to in .env)
PasswordPOSTGRES_PASSWORD from .env (tick Save password)

The Liberty schema lives in the liberty database by default. From there it's the usual pgAdmin tree: schemas → tables → query tool.


Common operator tasks via the bundled tools

I need to…ToolPath
Tail liberty-next logs while reproducing a bugPortainerContainers → liberty-nextLogsAuto-refresh
Restart Liberty after editing .envPortainerContainers → liberty-nextRecreate (so the new env is read)
Recreate Liberty pulling the latest imagePortainerContainers → liberty-nextRecreate → tick Pull latest image
See how big the Postgres volume has grownPortainerVolumes → pg-data → size column
Run an ad-hoc SELECT on the Liberty DBpgAdminServers → liberty → Databases → liberty → Query Tool
Check that a migration created its tablepgAdminServers → liberty → Databases → liberty → Schemas → public → Tables
Watch live Postgres sessionspgAdminServers → liberty → Dashboard → Server activity

For everything else (admin password resets, license inspection, liberty-admin commands) the table in Docker → Common operations still applies — those are CLI workflows.


Swarm specifics

In the Swarm layout, both Portainer and pgAdmin are deployed the same way, with two extra constraints:

ConstraintWhy
Portainer pinned to node.role == managerIt needs /var/run/docker.sock, which only managers expose. The compose sets this for you.
pgAdmin pinned to a manager (or wherever pg runs)The pgadmin-data volume must reattach in place — same logic as pg.
docker-compose.swarm.yml (portainer deploy block)
portainer:
image: portainer/portainer-ce:latest
command: -H unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer-data:/data
deploy:
replicas: 1
placement:
constraints:
- node.role == manager

Don't try to scale Portainer beyond replicas: 1 — it stores its admin DB on portainer-data and has no built-in clustering. Same for pgAdmin.


Common pitfalls

PitfallWhat actually happensAvoid by
Recreating liberty-next to reload a TOML edit.Portainer's Recreate kills and restarts the container — and TOML changes pushed via the Settings UI are already live. Useless restart, ~10 s of downtime.TOMLs reload on save. The framework-wide button is POST /admin/reload (or Settings → Reload). Only recreate to apply a new image tag or a .env change.
Restarting pg without first stopping liberty-next.Liberty's pool sees half its connections drop mid-query; the next requests fail until the pool recovers.Stop liberty-next first (Portainer → Stop), restart pg, start liberty-next. Or restart the whole stack in order.
Editing env vars in Portainer.Portainer's env tab is read-only — there's no Save button. Operators sometimes copy them out, edit, paste back, and nothing happens.Edit .env on the host, then Recreate the container so the new values are read at startup.
Exposing /portainer to the public internet before completing the wizard.The first stranger to load the page claims the admin account.Run the wizard immediately after install.sh full. Or firewall the path until you have.
Treating the Docker socket as harmless.It isn't — Portainer + socket = root on every container, including Liberty's env (master key, JWT secret, DB password).Strong Portainer admin password; consider deleting the service entirely if no one needs it.

When to remove the bundled tools

The bundled services are convenient, not mandatory. Remove them if:

  • Compliance forbids the Docker socket inside the application stack. Delete the portainer: block + portainer-data: volume; manage Docker from a separate, locked-down host.
  • You have a central pgAdmin / DBeaver / DataGrip already. Delete the pgadmin: block + pgadmin-data: volume; connect your central tool to the host's exposed 5432 port (or via SSH tunnel).
  • You're tight on RAM (≤ 2 GB). Each tool adds ~50–150 MB resident — small but not zero.

After removing either service:

docker compose up -d # COMPOSE_FILE picks the right files; reconciles the stack
docker volume rm <removed-volume> # only if you're sure

The rest of the Liberty stack (liberty-next, pg, traefik) is unaffected.


What's next

  • Docker → Full — the full layout overview, including the volume table.
  • Docker → Swarm — Swarm-specific placement + deploy notes.
  • Traefik — add TLS in front of /portainer and /pgadmin (same Host rule as liberty-next).
  • Production — hardening checklist (admin password rotation, source-IP allow-lists, off-host monitoring).