# Pulsys local dev — single command: docker compose up --build # # Console: http://localhost:3000 (admin SPA + /auth + /admin API) # HF proxy: http://localhost:8082 (model cache ingress) # Keycloak: http://localhost:8081 (dev IdP login UI only) # # Dev IdP user (Keycloak only, not a Pulsys password): # admin@pulsys.local / admin name: pulsys services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: pulsys POSTGRES_PASSWORD: pulsys POSTGRES_DB: pulsys volumes: - pulsys_pg:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U pulsys -d pulsys"] interval: 2s timeout: 5s retries: 30 keycloak: image: quay.io/keycloak/keycloak:26.0.5 command: start-dev --import-realm --health-enabled=true environment: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin # Full-URL form pins every emitted URL + `iss` claim to the browser path. KC_HOSTNAME: http://localhost:8081 KC_HOSTNAME_STRICT: "false" # Lets backchannel URLs (jwks_uri, token introspection) follow the # request's Host header so the pulsys container can fetch JWKS via # `keycloak:8080` while the browser still uses `localhost:8081`. KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true" KC_HTTP_ENABLED: "true" ports: - "8081:8080" volumes: - ./docker/keycloak/pulsys-realm.json:/opt/keycloak/data/import/pulsys-realm.json:ro healthcheck: # Keycloak 26 serves /health/ready on the management port (9000), not 8080. test: [ "CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && printf 'GET /health/ready HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3 && timeout 1 cat <&3 | grep -q '200'", ] interval: 5s timeout: 5s retries: 30 start_period: 60s init: build: context: . dockerfile: docker/Dockerfile target: init environment: PULSYS_DB_DSN: postgres://pulsys:pulsys@postgres:5432/pulsys?sslmode=disable PULSYS_OIDC_ISSUER: http://localhost:8081/realms/pulsys PULSYS_KEYCLOAK_INTERNAL_URL: http://keycloak:8080 PULSYS_OIDC_REDIRECT_URI: http://localhost:3000/auth/oidc/callback depends_on: postgres: condition: service_healthy keycloak: condition: service_healthy restart: "no" pulsys: build: context: . dockerfile: docker/Dockerfile target: pulsys environment: PULSYS_DB_DSN: postgres://pulsys:pulsys@postgres:5432/pulsys?sslmode=disable PULSYS_TENANT: default PULSYS_LISTEN: :8080 PULSYS_ADMIN_LISTEN: 0.0.0.0:6060 PULSYS_PUBLIC_BASE_URL: http://localhost:8082 PULSYS_CACHE_DIR: /var/lib/pulsys/cache PULSYS_IMPORT_WORKER: "1" # Required: Pulsys's own read-only HF token, used for all upstream reads # (cold misses + imports). Pulsys never forwards client tokens upstream # and refuses to start without this. Export it before `docker compose up`. PULSYS_HF_TOKEN: ${PULSYS_HF_TOKEN:?export PULSYS_HF_TOKEN to a read-only Hugging Face token before running docker compose up} # Backend reaches the IdP via the docker network; the id_token `iss` # claim is still validated against the browser-facing issuer URL. PULSYS_OIDC_DISCOVERY_BASE: http://keycloak:8080/realms/pulsys volumes: - pulsys_cache:/var/lib/pulsys/cache depends_on: init: condition: service_completed_successfully ports: # Host 8082 avoids clashing with a locally running pulsys on :8080. - "8082:8080" healthcheck: test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:8080/healthz >/dev/null 2>&1 || exit 1"] interval: 5s timeout: 3s retries: 10 start_period: 10s console: build: context: . dockerfile: docker/Dockerfile target: console ports: - "3000:80" depends_on: pulsys: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/healthz >/dev/null 2>&1 || exit 1"] interval: 5s timeout: 3s retries: 10 volumes: pulsys_pg: pulsys_cache: