Configuration
How the stack is wired and every setting that matters, with pointers to the repo that owns each one.
Where to run this. Docker Compose commands run inside a clone of this repository (
git clone https://github.com/grand-stack/grandstack, thencd grandstack), with the repositories it builds cloned beside it. The quickstart has the exact steps.
Compose services
compose.yaml defines three services:
| Service | Image or build | Ports (host:container) | Notes |
|---|---|---|---|
neo4j | neo4j:2026.10-community | 7474:7474 (browser), 7687:7687 (bolt) | Data in the named volume neo4j-data. Healthcheck runs cypher-shell; the server waits for it. |
server | built from ../grandstack-server | 4000:4000 | Needs Neo4j healthy first. |
web | built from ../grandstack-web | 3000:8080 | Static files served by unprivileged nginx. |
Because the builds use ../grandstack-server and ../grandstack-web, the three repositories must sit side by side in one folder. The auth setup also builds from ../grandstack-auth, the agents setup from ../grandstack-agents, and the federation setup from ../grandstack-federation, so those repositories must be there too if you use them.
With authentication: compose.auth.yaml
An override file, used together with compose.yaml: docker compose -f compose.yaml -f compose.auth.yaml up --build. Development only. It changes and adds:
| Service | Change |
|---|---|
keycloak (new) | quay.io/keycloak/keycloak:26.8.1 in dev mode, imports ../grandstack-auth/keycloak/grandstack-realm.json, issuer fixed to http://localhost:8081/realms/grandstack, port 8081, healthcheck |
gateway (new) | built from ../grandstack-auth/gateway, in front of the server, port 4000 |
auth-client (new) | the demo client, built from ../grandstack-auth/client, port 3001 |
server | no host port; gets AUTH_JWKS_URL, AUTH_ISSUER, AUTH_AUDIENCE, SCHEMA_EXTRA_PATHS, SETUP_CYPHER_PATHS; mounts auth.graphql and auth-setup.cypher; a healthcheck; waits for Keycloak |
web | unchanged: still http://localhost:3000, still calling http://localhost:4000/graphql, which is now the gateway, so it is an anonymous client |
Ports in the auth setup: 3000 web, 3001 demo client, 4000 gateway, 8081 Keycloak, 7474 and 7687 Neo4j. Everything else about the auth settings (roles, permissions, rules, the issuer-versus-keys addresses) is documented in grandstack-auth (docs/how-it-works.md, docs/role-and-scope-model.md).
With agents: compose.agents.yaml
An override file, used together with compose.yaml: docker compose -f compose.yaml -f compose.agents.yaml up --build. It adds one service:
| Service | Detail |
|---|---|
agents-mcp (new) | built from ../grandstack-agents; MCP over streamable HTTP at http://localhost:4100/mcp; GRAPHQL_URL=http://server:4000/graphql; ALLOW_MUTATIONS from AGENTS_ALLOW_MUTATIONS (default false) |
It talks to the open server, so it needs the default stack and has no auth of its own. Keep port 4100 on your machine. To use it with the auth setup, give it a token as described in grandstack-agents (docs/adding-auth.md).
With federation: compose.federation.yaml
An override file, used together with compose.yaml: docker compose -f compose.yaml -f compose.federation.yaml up --build. It changes and adds:
| Service | Detail |
|---|---|
server | FEDERATION=true; mounts ../grandstack-federation/schema/federation.graphql (SCHEMA_EXTRA_PATHS) and seed/wikipedia-links.cypher (SETUP_CYPHER_PATHS); host port 4001 instead of 4000; a healthcheck |
wikipedia (new) | built from ../grandstack-federation; the Wikipedia subgraph, port 4010 |
gateway (new) | built from ../grandstack-federation/gateway; serves the composed supergraph on port 4000; waits for the server and the Wikipedia subgraph |
web | unchanged: still http://localhost:3000, still calling http://localhost:4000/graphql, which is now the gateway |
It needs internet for the Wikipedia part, and it replaces the default, auth and agents setups on port 4000. Ports in the federation setup: 3000 web, 4000 gateway, 4001 Neo4j subgraph, 4010 Wikipedia subgraph, 7474 and 7687 Neo4j.
Settings you are likely to change
| Setting | Where | Default | Effect |
|---|---|---|---|
NEO4J_PASSWORD | .env (copy from .env.example) | grandstack-dev | Neo4j password; used by Neo4j, the healthcheck and the server. Set before the first start: Neo4j stores the password in the volume. |
Server variables (SEED, SEED_PATH, SEED_PROBE_LABEL, REFRESH_COUNTS, CORS_ORIGIN, NEO4J_DATABASE, AUTH_*, SCHEMA_EXTRA_PATHS, SETUP_CYPHER_PATHS, …) | server.environment in compose.yaml | see the server’s docs/configuration.md | How the server behaves. Add the ones you need under server.environment. |
VITE_GRAPHQL_URL | web.build.args in compose.yaml | http://localhost:4000/graphql | The URL the browser uses to reach the server. Fixed at build time, so rebuild web after changing it. |
| Host ports | ports in compose.yaml | 3000, 4000, 7474, 7687 | Change the left side to avoid clashes. If you move the web port, also set the server’s CORS_ORIGIN to the new web origin. If you move the server port, change VITE_GRAPHQL_URL. |
The server’s own variable list lives in docs/configuration.md in grandstack-server; it is the source of truth.
Everyday commands
docker compose up --build # start everything (first run builds the images)
docker compose up -d # start in the background
docker compose logs -f server # follow one service
docker compose down # stop, keep the data
docker compose down -v # stop and DELETE the Neo4j data (the sample reseeds on next start)
Use your own schema and data with an override file: build your own app.
Podman
On macOS docker may actually be Podman. If commands cannot connect, start the VM with podman machine start and set DOCKER_HOST to the socket it prints. Compose works the same way. The stack in this repo was developed and tested under Podman.
Resetting data
The default stack has no authentication, and the app can edit and delete everything. (With the auth setup, editing needs a login with the right role.) To get the sample data back, run docker compose down -v then docker compose up (add the -f files you started with). A seed newer than the loaded one reloads by itself on server start; see the server’s docs/seeding.md.
If a port is already in use
If you already run Neo4j (or anything else) on one of these ports, compose may fail to bind it, or a client may reach the wrong server: on the machine this was built on, a local Neo4j listened on 127.0.0.1:7687 while the container’s forwarded port was bound as well, so bolt://localhost:7687 could hit either one. The stack itself is unaffected, because the server reaches Neo4j over the compose network (bolt://neo4j:7687). To avoid confusion, change the left side of the port mapping in compose.yaml (for example "17687:7687") before connecting a tool from your machine, and check what is listening with lsof -nP -iTCP:7687 -sTCP:LISTEN.
Ports at a glance
| URL | What |
|---|---|
| http://localhost:3000 | Web app |
| http://localhost:4000/graphql | GraphQL API and GraphiQL |
| http://localhost:4001/graphql | Neo4j subgraph (federation setup only) |
| http://localhost:4010/graphql | Wikipedia subgraph (federation setup only) |
| http://localhost:4100/mcp | Agents MCP server (agents setup only; /healthz for a check) |
| http://localhost:7474 | Neo4j Browser (user neo4j, password from .env) |
| http://localhost:3001 | Auth demo client (auth setup only) |
| http://localhost:8081 | Keycloak (auth setup only; admin console admin / admin) |
| bolt://localhost:7687 | Neo4j Bolt |
Source: grandstack/docs/configuration.md (this page is a copy; edit it in the repository)