Deploy
Team Hub ships as an all-in-one Docker image: Nginx (public entrypoint), the Team Hub API, Postgres (default database), and Redis (authentication throttling). The image listens on $PORT (default 8080) so it works on platforms such as GCP Cloud Run that inject a port at runtime.
For local development without the full image, you can still run Postgres and Redis via docker compose up -d and start Team Hub on the host — see Setup.
What is in the container
| Process | Default bind | Purpose |
|---|---|---|
| Nginx | $PORT (8080) | Reverse proxy to Team Hub |
| Team Hub | 127.0.0.1:8787 | Fastify API |
| Postgres | 127.0.0.1:5432 | Database (bundled by default) |
| Redis | 127.0.0.1:6379 | Auth throttling store |
On startup the entrypoint:
- Initializes bundled Postgres on first boot (creates the
harboruser and database). - Renders
/etc/team-hub/server.yamlfrom environment variables. - Runs
team-hub migrate, thenteam-hub start. - Starts Nginx on
$PORT.
Health checks should use GET /health (proxied through Nginx).
Bundled vs managed services
The default image starts Postgres and Redis inside the container. That is convenient for demos, smoke tests, and self-hosted Docker.
Cloud Run storage is ephemeral. Bundled Postgres data is lost when the revision is redeployed or the instance is recycled. For production on Cloud Run, disable bundled services and use Cloud SQL (Postgres) and Memorystore (Redis) instead. See Production on Cloud Run below.
Prerequisites
- Docker installed locally
- A GCP project with billing enabled (for Cloud Run)
gcloudCLI authenticated to your project- An Artifact Registry repository (or legacy Container Registry)
Enable required APIs:
gcloud services enable run.googleapis.com artifactregistry.googleapis.comCreate an Artifact Registry repo (once per project/region):
gcloud artifacts repositories create team-hub \
--repository-format=docker \
--location=REGIONBuild and push the image
From the repository root:
export PROJECT_ID=your-gcp-project
export REGION=us-central1
export IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/team-hub/team-hub:latest"
docker build -t "${IMAGE}" .
docker push "${IMAGE}"Configure Docker to authenticate with Artifact Registry if needed:
gcloud auth configure-docker "${REGION}-docker.pkg.dev"Local smoke test
Run the image locally before pushing:
docker build -t team-hub:local .
docker run --rm -p 8080:8080 \
-e TEAM_HUB_DB_PASSWORD=harbor \
team-hub:localIn another terminal:
curl -s http://127.0.0.1:8080/healthExpect JSON like {"status":"ok","version":"..."}.
Note: each fresh container gets a new Postgres data directory unless you mount a volume:
docker run --rm -p 8080:8080 \
-v team-hub-pgdata:/var/lib/postgresql/data \
team-hub:localAfter the container is running, use the CLI to create an admin user — see Using the CLI in the container.
Quick start on Cloud Run (evaluation)
Deploy with bundled Postgres and Redis for a quick trial. Do not rely on this for production data — use managed services instead.
gcloud run deploy team-hub \
--image "${IMAGE}" \
--region "${REGION}" \
--port 8080 \
--memory 2Gi \
--cpu 2 \
--min-instances 1 \
--allow-unauthenticated--min-instances 1keeps one warm instance so bundled Postgres is less likely to restart mid-session. Data is still not durable across redeploys.- Omit
--allow-unauthenticatedif the service should require authentication at the Cloud Run / IAP layer.
After deploy, open the service URL and verify health:
curl -s "$(gcloud run services describe team-hub --region "${REGION}" --format='value(status.url)')/health"Production on Cloud Run
For production, point Team Hub at managed Postgres and Redis and disable the bundled processes.
Environment variables
| Variable | Production value | Notes |
|---|---|---|
TEAM_HUB_START_POSTGRES | false | Use Cloud SQL |
TEAM_HUB_START_REDIS | false | Use Memorystore |
TEAM_HUB_DB_HOST | Cloud SQL host or socket path | See Cloud SQL section |
TEAM_HUB_DB_PORT | 5432 | |
TEAM_HUB_DB_USER | your DB user | |
TEAM_HUB_DB_PASSWORD | from Secret Manager | |
TEAM_HUB_DB_DATABASE | your database name | |
TEAM_HUB_REDIS_HOST | Memorystore IP | Requires VPC connector |
TEAM_HUB_REDIS_PORT | 6379 |
Store secrets in Secret Manager and mount them on the Cloud Run service rather than passing passwords on the command line.
Example deploy with external services (adjust hostnames and secret references):
gcloud run deploy team-hub \
--image "${IMAGE}" \
--region "${REGION}" \
--port 8080 \
--memory 1Gi \
--cpu 1 \
--min-instances 1 \
--set-env-vars "TEAM_HUB_START_POSTGRES=false,TEAM_HUB_START_REDIS=false,TEAM_HUB_DB_HOST=/cloudsql/PROJECT:REGION:INSTANCE,TEAM_HUB_DB_USER=teamhub,TEAM_HUB_DB_DATABASE=teamhub,TEAM_HUB_REDIS_HOST=10.0.0.5" \
--set-secrets "TEAM_HUB_DB_PASSWORD=teamhub-db-password:latest" \
--add-cloudsql-instances "PROJECT:REGION:INSTANCE" \
--vpc-connector "projects/PROJECT/locations/REGION/connectors/CONNECTOR"Cloud SQL (Postgres)
- Create a Cloud SQL Postgres instance.
- Create a database and user for Team Hub.
- Attach the instance to Cloud Run with
--add-cloudsql-instances. - Set
TEAM_HUB_DB_HOSTto the Unix socket path/cloudsql/PROJECT:REGION:INSTANCE(Cloud Run mounts this automatically when the instance is attached).
Run migrations before serving traffic. Options:
- Deploy once with a Cloud Run Job that runs
node dist/cli.js -c /etc/team-hub/server.yaml migratewith the same env and Cloud SQL attachment. - Run migrate from a one-off
docker runon a machine that can reach the database.
Memorystore (Redis)
Team Hub requires Redis for authentication throttling. Protected routes return 503 when Redis is unreachable.
- Create a Memorystore for Redis instance in the same VPC region.
- Configure a Serverless VPC Access connector.
- Attach the connector to the Cloud Run service and set
TEAM_HUB_REDIS_HOSTto the instance IP.
Firestore (alternative database)
To use Firestore instead of Postgres, set TEAM_HUB_DB_DRIVER=firestore and mount a service account key (or use workload identity). You still need Redis. See server.yaml.example at the repository root for the Firestore config shape; map fields to env vars or mount a custom server.yaml at /etc/team-hub/server.yaml via a volume (advanced).
LLM provider keys
Optional LLM proxy settings are not generated from env vars in the default template. For hub-proxied LLM access, mount a config file with an llm section or extend deployment tooling. See LLM and server.yaml.example at the repository root.
Using the CLI in the container
Administration commands (user, migrate, collection, and so on) run through the same CLI as a host install. In the Docker image, a few details differ from Setup.
Where the config file lives
At startup the entrypoint generates the config at /etc/team-hub/server.yaml. There is no server.yaml in /app (the app working directory). The running server is started with that path explicitly.
The CLI does not read the TEAM_HUB_CONFIG environment variable. You must pass the config path with -c / --config, or the CLI looks for server.yaml in the current directory and fails with “config file not found”.
Verify the generated config inside a running container:
docker exec CONTAINER cat /etc/team-hub/server.yamlHow to invoke the CLI
The team-hub binary is not on PATH in the image. Run the built CLI with Node from /app:
node /app/dist/cli.js -c /etc/team-hub/server.yaml <subcommand> [options]Put global flags before the subcommand. -c is a root-level option, not a subcommand option:
# Correct
node /app/dist/cli.js -c /etc/team-hub/server.yaml user list
# Wrong — "unknown option '-c'"
node /app/dist/cli.js user list -c /etc/team-hub/server.yamlRunning commands from your host
Prefer one-shot docker exec from your machine (no interactive shell required):
docker exec -it CONTAINER \
node /app/dist/cli.js -c /etc/team-hub/server.yaml user listReplace CONTAINER with the container name or id from docker ps.
Running commands inside the container
If you open a shell with docker exec -it CONTAINER bash, change to /app first:
cd /app
node dist/cli.js -c /etc/team-hub/server.yaml user listOptional alias for an interactive session:
alias team-hub='node /app/dist/cli.js -c /etc/team-hub/server.yaml'
team-hub user listSee CLI for all subcommands and options.
Restart the server after config changes
Team Hub loads server.yaml once at startup. It does not hot-reload config. After you change settings, restart the Team Hub process so they take effect.
| Scenario | Action |
|---|---|
Edited /etc/team-hub/server.yaml manually (e.g. added llm:) | Run the restart script — preserves your edits |
Changed Docker env vars (TEAM_HUB_DB_*, etc.) | docker restart CONTAINER from the host — entrypoint regenerates yaml from env |
| Full stack restart (Postgres, Redis, Nginx, Team Hub) | docker restart CONTAINER |
The image includes a restart helper (no pkill required — the slim image does not ship procps):
# Inside the container
/docker/restart-team-hub.sh
# or:
restart-team-hub
# From your host
docker exec CONTAINER /docker/restart-team-hub.shThe script sends SIGTERM to the running Team Hub start process. Supervisord respawns it, runs migrate, then start with the current config. Nginx, Postgres, and Redis are not restarted.
Verify after restart:
curl -s http://127.0.0.1:8080/healthCreate an admin user
After the container is healthy (GET /health returns {"status":"ok",...}), create the first admin account:
docker exec -it CONTAINER \
node /app/dist/cli.js -c /etc/team-hub/server.yaml user create --name ops --role adminList users to confirm and copy the user id:
docker exec -it CONTAINER \
node /app/dist/cli.js -c /etc/team-hub/server.yaml user listCreate an API token for HarborClient (replace USER_ID with the id from user list):
docker exec -it CONTAINER \
node /app/dist/cli.js -c /etc/team-hub/server.yaml user token create USER_ID --name desktopSee Authentication for token usage and CLI — Examples for other admin tasks.
Cloud Run
On Cloud Run there is no long-lived shell to exec into. Run admin commands with a Cloud Run Job or a one-off task using the same image, environment variables, and secrets as the service — for example node /app/dist/cli.js -c /etc/team-hub/server.yaml migrate or user create.
Post-deploy administration
Most day-two tasks use the CLI patterns above: always pass -c /etc/team-hub/server.yaml before the subcommand, and invoke node /app/dist/cli.js from /app. After editing config, restart Team Hub before expecting new settings to apply. Common follow-ups after creating a user:
user token create— issue bearer tokens for HarborClientuser token list/user token revoke— manage tokenscollection list— inspect synced collections
See CLI and Authentication for full reference.
Health checks
Cloud Run sends traffic to $PORT. Nginx proxies to Team Hub’s GET /health endpoint.
Use /health for manual checks and uptime monitoring. The response includes status: "ok" and the application version.
Environment variable reference
| Variable | Default | Description |
|---|---|---|
PORT | 8080 | Nginx listen port (set by Cloud Run) |
TEAM_HUB_PORT | 8787 | Internal Team Hub port |
TEAM_HUB_HOST | 127.0.0.1 | Team Hub bind address |
TEAM_HUB_CONFIG | /etc/team-hub/server.yaml | Generated config path |
TEAM_HUB_START_POSTGRES | true | Start bundled Postgres |
TEAM_HUB_START_REDIS | true | Start bundled Redis |
TEAM_HUB_DB_DRIVER | postgres | postgres, mysql, or firestore |
TEAM_HUB_DB_HOST | 127.0.0.1 | Database host |
TEAM_HUB_DB_PORT | 5432 | Database port |
TEAM_HUB_DB_USER | harbor | Database user |
TEAM_HUB_DB_PASSWORD | harbor | Database password |
TEAM_HUB_DB_DATABASE | harbor | Database name |
TEAM_HUB_REDIS_HOST | 127.0.0.1 | Redis host |
TEAM_HUB_REDIS_PORT | 6379 | Redis port |
Troubleshooting
Container exits during startup
Check Cloud Run logs or docker logs. Common causes:
- Insufficient memory — bundled Postgres + Redis + Node need at least 2 GiB for evaluation deploys.
- Postgres init failure — ensure
PGDATA(/var/lib/postgresql/data) is writable; on Cloud Run without a volume, first boot should still succeed but data is ephemeral.
GET /health fails or connection refused
- Confirm the service listens on
$PORT(8080). - Wait for startup: migrations and Postgres init can take 30–60 seconds on cold start.
Protected API routes return 503
Redis is required for auth throttling. Verify Redis is running (bundled) or reachable (Memorystore + VPC connector). See Authentication.
Config file not found
The CLI defaults to server.yaml in the current directory. In the container the generated config is at /etc/team-hub/server.yaml. Pass it explicitly before the subcommand:
node /app/dist/cli.js -c /etc/team-hub/server.yaml user listIf /etc/team-hub/server.yaml itself is missing, the container likely failed during startup — check docker logs CONTAINER.
Migration errors
- Ensure the database user can create tables.
- Run
team-hub migratemanually with the same config the server uses. - For Cloud SQL, confirm the Cloud SQL Auth proxy / Unix socket attachment is configured.
Stale data after redeploy on Cloud Run
Expected when using bundled Postgres. Switch to Cloud SQL for durable storage.
Related docs
- Setup — install and run on the host
- Authentication — bearer tokens and Redis throttling
- CLI — users, tokens, collections
server.yaml.exampleat the repository root — full configuration reference
