Skip to content

Install with Docker Compose

This page takes you from an empty server to a running Gophenberg. You need Docker with Compose, and a reverse proxy in front holding your HTTPS certificate, such as Caddy, Traefik, or nginx.

Create a directory on your server with this compose.yaml:

services:
db:
image: postgres:18
environment:
POSTGRES_DB: gophenberg
POSTGRES_PASSWORD: 'change-me'
volumes:
- db-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 2s
retries: 15
gophenberg:
image: ghcr.io/gopherium/gophenberg:0.18.0
restart: unless-stopped
stop_grace_period: 30s
environment:
GOPHENBERG_DATABASE_URL: postgres://postgres:change-me@db:5432/gophenberg?sslmode=disable
GOPHENBERG_SITE_TITLE: My Site
GOPHENBERG_TRUSTED_PROXIES: 172.16.0.0/12
GOPHENBERG_PUBLIC_URL: https://example.com
volumes:
- themes:/themes
- media:/media
ports:
- "127.0.0.1:8081:8081"
depends_on:
db:
condition: service_healthy
volumes:
db-data:
themes:
media:

Four values to change:

  • The password, in both places it appears. Keep the POSTGRES_PASSWORD value in single quotes, so a password such as 0012345678 is not read as a number. A long password of only letters and digits is simplest, because it needs nothing more. Otherwise write a $ as $$ in both places, a ' as '' in POSTGRES_PASSWORD, and in GOPHENBERG_DATABASE_URL also write an @ as %40, a / as %2F, a % as %25 and a space as %20.
  • The image tag. Pin the newest version from the tags page, never latest.
  • GOPHENBERG_TRUSTED_PROXIES, the network your proxy connects from, in CIDR notation. The value above fits a proxy in Docker and a proxy on the host, which reaches the container through the same Docker bridge.
  • GOPHENBERG_PUBLIC_URL, your own domain as people type it. Every write sent to another address is refused, see the public address.

The healthcheck and the condition keep Gophenberg from starting before the database is ready on first boot. The themes volume is where themes you upload in the admin are kept, so it has to stay writable. The stop_grace_period gives a stop time to finish before Docker kills the server, see stopping the server.

Terminal window
docker compose up -d
docker compose run --rm -T gophenberg \
account:create-admin -email admin@example.com -name "Maria Perez" -role admin

Migrations run at startup, so there is no setup step. account:create-admin prints Password: and reads the password from its input, keeping it out of your shell history. Type it and press Enter, or pipe it in, for example from a file only you can read by adding < password.txt at the end of the command. The -role flag says what the account may do, and takes admin, editor or author. Give the first account admin, the role that can reach everything, because only an admin can manage the other accounts.

The image runs serve when it is given no command, which is what docker compose up does. Any other command runs the way the one above does, in a container of its own that --rm removes when the command ends. For example, this lists every command:

Terminal window
docker compose run --rm -T gophenberg list

If your setup passes the image its own arguments, such as a command: line in the compose file, name serve first. Run with no command at all, Gophenberg only lists its commands and stops. Every command and its flags are on the commands page.

That is everything a new site needs. Upgrading a site that ran an earlier version needs two manual steps first, renaming the role column and giving a role to the accounts that hold none, both covered by Users and signing in. Skipping the first one leaves a site that starts without complaint and then refuses every login.

Forward your domain to 127.0.0.1:8081. With Caddy:

example.com {
reverse_proxy 127.0.0.1:8081
}
  • Your domain shows the public site.
  • curl -sI https://example.com | grep -i x-generator answers with Gophenberg 0.18. The -i matters, HTTP/2 lowercases header names.
  • /admin/ loads and your login works.

From here: configuration lists every setting, and installing a theme changes how the site looks.