Installation
The Docker Compose bundle runs a complete GOAT on one Linux server: the web app and its APIs, the login server (Keycloak), object storage (Garage), the database (PostgreSQL with PostGIS), the job engine (Windmill) and a proxy (Caddy) that handles HTTPS. An installation takes three commands:
./setup.sh && docker compose up -d && ./smoke.sh
This page walks you through them, from the requirements to your first login.
Requirementsβ
| Minimum (trial) | Recommended | |
|---|---|---|
| CPU / RAM | 4 vCPU / 8 GB | 8 vCPU / 16 GB |
| Disk | 50 GB | 200 GB or more (grows with your data) |
| Operating system | Any Linux with Docker Engine 24+ and Docker Compose 2.23+ | Ubuntu 24.04 LTS |
In addition, you need:
- Open ports 80 and 443, or the one port you choose for plain HTTP. Nothing else has to be reachable from outside.
- A DNS name pointing at the server if you want automatic HTTPS with Let's Encrypt.
- Outgoing internet access to pull the images from
ghcr.io, and for the routing base data, the basemaps and the optional integrations. opensslforsetup.sh, andcurlandjqforsmoke.sh.
The GOAT images are public. You do not need to log in to a registry.
1. Download the bundleβ
Every GOAT release publishes the bundle as goat-compose-<version>.tar.gz on its GitHub release page. The bundle is pinned to its release: it installs exactly that GOAT version.
curl -LO https://github.com/plan4better/goat/releases/download/<version>/goat-compose-<version>.tar.gz
tar xzf goat-compose-<version>.tar.gz
cd goat-compose
Replace <version> with the release tag you want to install. The folder contains:
| File | Purpose |
|---|---|
compose.yaml | All services |
.env.example | Every setting, with comments. setup.sh creates .env from it. |
setup.sh | Writes and updates .env |
smoke.sh | Checks the running installation end to end |
base-data.sh | Downloads the routing and public transport base data |
restore.sh | Restores a backup |
certs/ | Your own certificate and CA files, if you use any |
init/ | Configuration of the bundled services (proxy, database, login, storage, jobs) |
Run every command on this page from inside this folder.
2. Create the configurationβ
setup.sh creates the file .env, which holds the whole configuration. Run it without options and it asks four questions:
./setup.sh
| Question | Default |
|---|---|
Public URL: the address users type into the browser, such as https://goat.example.org or http://10.0.0.5 | http://localhost |
TLS mode: auto, custom, internal or off (see HTTPS and addresses) | off for an http:// URL, otherwise auto |
Email for Let's Encrypt notices (only asked in auto mode, optional) | empty |
| Email of the first GOAT administrator | admin@<your host> |
setup.sh then generates random passwords and keys for every component, stores them in .env (readable only by your user), and prints the first administrator's login:
Wrote .env for https://goat.example.org (TLS: auto).
First login: admin@example.org / <generated password> (also stored in .env as GOAT_ADMIN_PASSWORD)
Next: docker compose up -d && ./smoke.sh
Without questionsβ
For scripted installs, pass the answers as options and add --non-interactive:
./setup.sh --public-url https://goat.example.org --tls auto \
--acme-email ops@<your-domain> --admin-email admin@<your-domain> \
--non-interactive
| Option | Meaning |
|---|---|
--public-url URL | The public URL, with http:// or https://, without a path |
--tls auto|custom|internal|off | The TLS mode |
--acme-email EMAIL | Contact for Let's Encrypt expiry notices (auto only) |
--admin-email EMAIL | Email of the first administrator |
--http-port PORT | Port for plain HTTP when TLS ends at your own load balancer (see Behind your own load balancer) |
--non-interactive | Ask nothing; use the options and the values already in .env |
-h, --help | Show the usage |
setup.sh stops with a clear message if something does not fit, for example:
- the URL has no
http://orhttps://, or contains a path (it must be a bare origin such ashttps://goat.example.org); - an
http://URL is combined with a TLS mode other thanoff; automode is combined with a port in the URL (Let's Encrypt needs the standard ports);- the Let's Encrypt email uses a domain that Let's Encrypt rejects (see Automatic certificate);
- Docker Compose is older than 2.23.
Running setup.sh againβ
You can run setup.sh as often as you like. It saves a copy of the current file as .env.bak.<timestamp>, keeps every value already in .env, adds settings that are new in .env.example, and generates secrets only where they are empty. The only values it rewrites are the ones it derives from the public URL and the TLS mode.
.env safe.env contains every password and key of your installation. Keep a copy with your backups: a restored database and storage expect exactly these values.
3. Start GOATβ
docker compose up -d
The first start takes a few minutes: Docker pulls the images, PostgreSQL creates the databases, Keycloak creates the login realm with the first administrator, and the analysis tools are registered in Windmill.
A few services only prepare the installation and then stop: data-init, core-migrate, ducklake-init, garage-init, windmill-bootstrap and windmill-sync. They run on every docker compose up -d, are safe to repeat, and apply database migrations after an upgrade. docker compose ps -a shows them as Exited (0) when they succeeded.
4. Check the installationβ
./smoke.sh
smoke.sh checks the whole installation step by step and prints PASS or FAIL for each step:
- All one-time services finished successfully, and all long-running services are healthy.
- The web app, the Core API, GeoAPI, Processes and Catalog answer under the public URL, and GeoAPI builds its links with the public URL.
- Keycloak announces the public URL as its issuer, and the first administrator can log in.
- The administrator belongs to an organization and has a profile.
- A small GeoJSON file is uploaded, imported as a layer, served as a vector tile, and used in a buffer analysis.
./smoke.sh --quick runs steps 1 to 4 only. If a step fails, smoke.sh stops, names the step and prints the last log lines of the service involved.
smoke.sh works with the account of the first administrator. It deletes the test datasets it imports again at the end, so nothing stays in My Content. If the administrator has no organization yet, it creates one named GOAT; to choose the name yourself, sign in first (step 5) and run smoke.sh afterwards, or rename it later under Settings β Organization β Profile. ./smoke.sh --quick only checks the services and the login and changes nothing.
5. Sign inβ
Open the public URL in your browser and sign in with the email and password that setup.sh printed. The password is also stored in .env as GOAT_ADMIN_PASSWORD.
If the administrator has no organization yet (because you signed in before running smoke.sh), GOAT first asks you to create one on the page Setup a new organization. It has three steps, Name & Region, Profile and Contact; click Let's get started to finish.
To add more users, see Users and the Keycloak admin console.
Where the services liveβ
All services share the one public URL and are told apart by the path:
| Path | Service |
|---|---|
/ | Web app |
/api | Core API (/api/v2/β¦, health check at /api/healthz) |
/geoapi | GeoAPI |
/processes | Processes |
/catalog | Catalog (STAC API at /catalog/stac) |
/keycloak | Login (Keycloak); admin console at /keycloak/admin |
With the bundled object storage, /goat-uploads and /goat-assets lead to Garage for uploads and public images.
Only Caddy is reachable from outside. PostgreSQL, Redis, the Garage admin interface and the Windmill interface are not exposed; see Operations for how to reach Windmill.
Next stepsβ
- Download the routing base data for your region, so that catchment areas, heatmaps and the public transport tools work.
- Set up email, so that GOAT can send invitations and Keycloak can send password resets.
- Switch on backups.