Deployment
The included Docker Compose stack provides the infrastructure used by
stac-catalog: PostgreSQL/pgSTAC, STAC FastAPI, TiTiler, and
STAC Browser.
For the CLI workflow, see CLI reference. For input fields and filename conventions, see Metadata.
Architecture
stac-catalog
│
┌───────────────┴────────────────┐
│ │
Local development Production
│ │
│ direct host access │ --prod
│ ▼
│ generator container
│ │
└───────────────┬────────────────┘
│
private Docker network
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
PostgreSQL / pgSTAC TiTiler STAC FastAPI
│ │
│ ▼
│ STAC Browser
│
local: 127.0.0.1:5432
prod: no published port
Locally, the host stac-catalog CLI connects to pgSTAC through a
loopback-only PostgreSQL port. In production, stac-catalog load --prod and
stac-catalog sync --prod run the database-dependent operation inside the
generator container, so PostgreSQL stays private on the Docker network.
Services
| Service | Purpose | Local development address |
|---|---|---|
db | PostgreSQL with pgSTAC | 127.0.0.1:5432 |
api | STAC FastAPI backed by pgSTAC | http://localhost:8081 |
titiler | Raster rendering and preview API | http://localhost:8000 |
browser | STAC Browser | http://localhost:8082 |
generator | Containerized stac-catalog CLI used by --prod | no persistent port |
The generator is an on-demand tool (it is in the tools compose profile), so
it does not run with a normal docker compose up.
Environment
cp .env.example .env
POSTGRES_DB=stac
POSTGRES_USER=postgres
POSTGRES_PASSWORD=stac
Do not commit .env. Inside the Docker network, pgSTAC is reached as:
POSTGRES_HOST=db
POSTGRES_PORT=5432
Local development
For local development, the host CLI needs access to PostgreSQL and TiTiler.
Bind PostgreSQL only to the loopback interface (already the default in the compose file):
ports:
- "127.0.0.1:5432:5432"
Start and check the stack:
docker compose config # validate
docker compose up -d # starts db, api, titiler, browser
docker compose ps # check status
A typical local session is: prepare input/, run
stac-catalog sync --source csv --input ./input --output ./output, then browse
at http://localhost:8082. See Getting started.
Production
Production should keep PostgreSQL private inside the Docker network — only the services that need to be public are routed through the reverse proxy.
Private PostgreSQL
Remove the ports block from the db service in production:
db:
image: ghcr.io/stac-utils/pgstac:v0.9.8
environment:
POSTGRES_DB: ${POSTGRES_DB:-stac}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
networks:
- stac
There should be no ports: block (no 5432:5432 or
127.0.0.1:5432:5432) on the production db service. The database is then
reachable only as db:5432 from containers on the private Compose network.
Production CLI
Use the same CLI with --prod (see CLI reference → --prod):
# Load previously generated output
stac-catalog load --prod
# Generate + load CSV metadata
stac-catalog sync --prod --source csv
# Generate + load from a Google Sheet
stac-catalog sync --prod --source gsheet --config stac-config.yaml
--prod handles the container execution needed to reach private pgSTAC, so you
do not need to run docker compose run --rm manually. It uses the standard
mounted paths (./input → /data/input, ./output → /data/output), and for
Google Sheets it also mounts the config and referenced credentials. Do not
pass --input, --output, or --dsn with --prod.
Service exposure
A typical deployment exposes three public services, each behind HTTPS:
https://stac.example.org → STAC Browser
https://stac-api.example.org → STAC FastAPI
https://tiles.example.org → TiTiler
PostgreSQL → private Docker network only
STAC FastAPI can remain a read-only public API. The production loading workflow does not require exposing PostgreSQL or enabling public STAC Transactions write endpoints.
Public URLs
Two settings should change for a public deployment.
STAC Browser — point SB_catalogUrl at the public STAC API:
SB_catalogUrl: "https://stac-api.example.org"
TiTiler — keep the internal address, but write the public URL into STAC metadata:
TITILER_URL: http://titiler:8000 # internal only
TITILER_PUBLIC_URL: https://tiles.example.org # written into STAC assets
http://titiler:8000 works only inside the Compose network and should not be
written into public STAC assets.
Lifecycle
Logs
docker compose logs -f # all long-running services
docker compose logs -f api db titiler browser # specific services
docker compose logs --tail=100 db api titiler browser # recent lines only
Restart
docker compose restart api # one service
docker compose restart api browser # several
docker compose up -d --force-recreate # recreate after a compose change
docker compose build generator # rebuild after Python/Dockerfile changes
Stop
docker compose stop # stop without removing containers
docker compose start # start them again
docker compose down # remove containers + network, keep the DB volume
Database persistence
pgSTAC data is stored in the named volume stac_db. docker compose down
preserves this volume. Inspect volumes with docker volume ls.
:::danger docker compose down -v deletes the database
The -v flag deletes Compose volumes — including the pgSTAC data. Only use it
when you intend a full reset.
:::
To reset the local database intentionally:
docker compose down -v
docker compose up -d
stac-catalog sync --source csv --input ./input --output ./output
Verify the stack
Local:
curl http://localhost:8081/ # STAC API landing
curl http://localhost:8081/collections # collections
curl -I http://localhost:8000/docs # TiTiler docs
# STAC Browser: http://localhost:8082
Production: use the configured public domains, and confirm with
docker compose ps that the db service shows no host mapping such as
0.0.0.0:5432->5432/tcp or 127.0.0.1:5432->5432/tcp.
Production checklist
For a public deployment:
- set a strong PostgreSQL password;
- do not publish the PostgreSQL port;
- put STAC FastAPI, STAC Browser, and TiTiler behind HTTPS/reverse proxy;
- update
SB_catalogUrlto the public STAC API URL; - update
TITILER_PUBLIC_URLto the public TiTiler URL; - keep
TITILER_URL=http://titiler:8000for internal container communication; - use
stac-catalog load --prodorstac-catalog sync --prodfor private database access; - restrict network and firewall exposure;
- back up the PostgreSQL/pgSTAC data volume (
stac_db); - consider pinning
stac-fastapi-pgstac, TiTiler, and STAC Browser image versions instead of usinglatest.