diff --git a/.changeset/hip-poems-shave.md b/.changeset/hip-poems-shave.md
new file mode 100644
index 000000000..a22e5f5da
--- /dev/null
+++ b/.changeset/hip-poems-shave.md
@@ -0,0 +1,6 @@
+---
+"@ensnode/integration-test-env": patch
+"@docs/ensnode": patch
+---
+
+Moved docker-compose.yml file to separate `docker` directory, updates docs and cicd-tests
diff --git a/README.md b/README.md
index e56e1a5ca..12dc0e6d8 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,14 @@ ENSNode is a modern, multichain indexer for ENS. It supports backwards-compatibl
Documentation for the ENSNode suite of apps is available at [ensnode.io](https://ensnode.io).
+## Running with Docker
+
+```bash
+docker compose -f docker/docker-compose.yml up -d
+```
+
+See [`docker/README.md`](docker/README.md) for all use cases and commands.
+
## Contributions
We welcome community contributions and feedback—please see [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
diff --git a/apps/ensindexer/.env.local.example b/apps/ensindexer/.env.local.example
index cd5b3d948..412aa88dc 100644
--- a/apps/ensindexer/.env.local.example
+++ b/apps/ensindexer/.env.local.example
@@ -243,6 +243,7 @@ ENSRAINBOW_URL=http://localhost:3223
# https://ensnode.io/ensrainbow/usage/available-label-sets
#
# LABEL_SET_ID: see https://ensnode.io/ensrainbow/concepts/glossary#label-set-id.
+# Should match ENSRainbow's LABEL_SET_ID.
LABEL_SET_ID=subgraph
# LABEL_SET_VERSION: see https://ensnode.io/ensrainbow/concepts/glossary#label-set-version.
diff --git a/apps/ensrainbow/.env.local.example b/apps/ensrainbow/.env.local.example
index 7eb88231b..837519561 100644
--- a/apps/ensrainbow/.env.local.example
+++ b/apps/ensrainbow/.env.local.example
@@ -16,7 +16,7 @@ LOG_LEVEL=info
DB_SCHEMA_VERSION=3
# Label set ID (see https://ensnode.io/ensrainbow/concepts/glossary#label-set-id)
-LABEL_SET_ID=ens-test-env
+LABEL_SET_ID=subgraph
# Label set version (see https://ensnode.io/ensrainbow/concepts/glossary#label-set-version)
LABEL_SET_VERSION=0
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 1f04274da..000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,127 +0,0 @@
-services:
- ensindexer:
- container_name: ensindexer
- image: ghcr.io/namehash/ensnode/ensindexer:latest
- ports:
- - "42069:42069"
- environment:
- # Override environment variables to point to docker instances
- ENSDB_URL: postgresql://postgres:password@postgres:5432/postgres
- ENSINDEXER_SCHEMA_NAME: docker_compose_ensindexer_schema
- ENSRAINBOW_URL: http://ensrainbow:3223
- env_file:
- # NOTE: must define apps/ensindexer/.env.local (see apps/ensindexer/.env.local.example)
- # Copy .env.local.example to .env.local and configure all required values
- - path: ./apps/ensindexer/.env.local
- required: true
- healthcheck:
- test: ["CMD", "curl", "--fail", "-s", "http://localhost:42069/health"]
- interval: 30s
- timeout: 10s
- retries: 3
- start_period: 5m
- start_interval: 1s
- depends_on:
- ensrainbow:
- condition: service_healthy
- postgres:
- condition: service_started
-
- ensapi:
- container_name: ensapi
- image: ghcr.io/namehash/ensnode/ensapi:latest
- ports:
- - "4334:4334"
- environment:
- # Override environment variables to point to docker instances
- ENSDB_URL: postgresql://postgres:password@postgres:5432/postgres
- ENSINDEXER_SCHEMA_NAME: docker_compose_ensindexer_schema
- env_file:
- # NOTE: must define apps/ensapi/.env.local (see apps/ensapi/.env.local.example)
- # Copy .env.local.example to .env.local and configure all required values
- - path: ./apps/ensapi/.env.local
- required: true
- healthcheck:
- test: ["CMD", "curl", "--fail", "-s", "http://localhost:4334/health"]
- interval: 30s
- timeout: 10s
- retries: 3
- start_period: 1m
- start_interval: 1s
- depends_on:
- postgres:
- condition: service_started
-
- ensrainbow:
- container_name: ensrainbow
- image: ghcr.io/namehash/ensnode/ensrainbow:latest
- ports:
- - "3223:3223"
- env_file:
- # NOTE: Optionally define apps/ensrainbow/.env.local (see apps/ensrainbow/.env.local.example)
- - path: ./apps/ensrainbow/.env.local
- required: false
- volumes:
- - ensrainbow_data:/app/apps/ensrainbow/data
- restart: unless-stopped
- healthcheck:
- test: ["CMD", "wget", "-q", "--spider", "http://localhost:3223/health"]
- interval: 30s
- timeout: 3s
- retries: 3
- start_period: 20m
- start_interval: 1s
-
- ensadmin:
- container_name: ensadmin
- image: ghcr.io/namehash/ensnode/ensadmin:latest
- ports:
- - "4173:4173"
- environment:
- # Override environment variables to point to docker instances
- # NOTE: must be host-accessible (i.e. http://localhost)
- ENSADMIN_PUBLIC_URL: http://localhost:4173
- # NOTE: must be host-accessible (i.e. http://localhost)
- NEXT_PUBLIC_SERVER_CONNECTION_LIBRARY: http://localhost:4334
- env_file:
- # NOTE: can define apps/ensadmin/.env.local (see apps/ensadmin/.env.local.example)
- - path: ./apps/ensadmin/.env.local
- required: false
- depends_on:
- ensapi:
- condition: service_started
-
- postgres:
- container_name: postgres
- image: postgres:17
- environment:
- POSTGRES_DB: postgres
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: password
- ports:
- - "5432:5432"
- volumes:
- - postgres_data:/var/lib/postgresql/data
-
- devnet:
- container_name: devnet
- image: ghcr.io/ensdomains/contracts-v2:main-e8696c6
- command: ./script/runDevnet.ts --testNames
- pull_policy: always
- ports:
- - "8545:8545"
- environment:
- ANVIL_IP_ADDR: "0.0.0.0"
- healthcheck:
- test: ["CMD", "curl", "--fail", "-s", "http://localhost:8000/health"]
- interval: 10s
- timeout: 5s
- retries: 5
- start_period: 30s
- start_interval: 1s
-
-volumes:
- postgres_data:
- driver: local
- ensrainbow_data:
- driver: local
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 000000000..ac8e8f6ba
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,90 @@
+# Docker Compose
+
+All commands are run from the **monorepo root**.
+
+## Files
+
+| File | Purpose |
+| ---------------------------------------- | -------------------------------------------------------------------------------------- |
+| `docker/docker-compose.yml` | Base stack — ensindexer, ensapi, ensrainbow, ensadmin, postgres. For mainnet/sepolia. |
+| `docker/docker-compose.devnet.yml` | Full stack against local devnet (`ens-test-env`). Includes all base services + devnet. |
+| `docker/docker-compose.orchestrator.yml` | Minimal infra for CI — devnet + postgres only. Used by `orchestrator.ts`. |
+| `docker/services/*.yml` | Individual service definitions. Extended by the compose files above. |
+| `docker/envs/.env.docker.common` | Shared env defaults (postgres credentials, internal service URLs). Committed. |
+| `docker/envs/.env.docker.devnet` | Devnet defaults (PLUGINS, etc.). Committed. Works out of the box. |
+| `docker/envs/.env.docker.example` | Example for user-specific config. Copy to `.env.docker.local` for mainnet/sepolia. |
+| `docker/envs/.env.docker.local` | User config (gitignored). Required for base stack, optional for devnet overrides. |
+
+> To inspect the fully resolved config for any compose file (resolves all `extends`):
+>
+> ```
+> docker compose -f docker/docker-compose.yml config
+> ```
+
+## Use cases
+
+### Mainnet / Sepolia
+
+**1. Configure environment** (one-time setup):
+
+```bash
+cp docker/envs/.env.docker.example docker/envs/.env.docker.local
+```
+
+Edit `docker/envs/.env.docker.local` and set `NAMESPACE`, `PLUGINS`, and your RPC endpoints (e.g. `ALCHEMY_API_KEY` or `RPC_URL_1`).
+
+**2. Start/stop the stack:**
+
+```bash
+# Start full stack in background
+docker compose -f docker/docker-compose.yml up -d
+
+# Stop
+docker compose -f docker/docker-compose.yml down
+
+# Stop and remove volumes
+docker compose -f docker/docker-compose.yml down -v
+```
+
+### Local devnet (for developers)
+
+No setup required — devnet defaults are committed in `docker/envs/.env.docker.devnet`.
+
+To override defaults (e.g. change `PLUGINS`), create `docker/envs/.env.docker.local` with your values.
+
+```bash
+# Start full stack against devnet
+docker compose -f docker/docker-compose.devnet.yml up -d
+
+# Start only devnet + core services (no ensadmin)
+docker compose -f docker/docker-compose.devnet.yml up -d devnet postgres ensrainbow ensindexer ensapi
+
+# Start only devnet (quick local EVM node, also shows data information about devnet)
+docker compose -f docker/docker-compose.devnet.yml up devnet
+# or
+pnpm devnet
+
+# Stop
+docker compose -f docker/docker-compose.devnet.yml down
+```
+
+### Build images locally
+
+```bash
+# Build all images
+pnpm docker:build:ensnode
+
+# Build a specific image
+pnpm docker:build:ensindexer
+pnpm docker:build:ensapi
+pnpm docker:build:ensrainbow
+pnpm docker:build:ensadmin
+```
+
+### CI / integration tests
+
+Used internally by `orchestrator.ts` via testcontainers. Starts devnet + postgres only.
+
+```bash
+pnpm test:integration:ci
+```
diff --git a/docker/docker-compose.devnet.yml b/docker/docker-compose.devnet.yml
new file mode 100644
index 000000000..76e79373c
--- /dev/null
+++ b/docker/docker-compose.devnet.yml
@@ -0,0 +1,100 @@
+services:
+ ensindexer:
+ extends:
+ file: services/ensindexer.yml
+ service: ensindexer
+ environment:
+ # TODO: in future we will migrate devnet to chain_id=1
+ # need to update this line in that case
+ RPC_URL_15658733: http://devnet:8545
+ ENSINDEXER_SCHEMA_NAME: docker_devnet_v1
+ LABEL_SET_ID: ens-test-env
+ NAMESPACE: ens-test-env
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.devnet
+ required: true
+ - path: envs/.env.docker.local
+ required: false
+ depends_on:
+ ensrainbow:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ devnet:
+ condition: service_healthy
+
+ ensapi:
+ extends:
+ file: services/ensapi.yml
+ service: ensapi
+ environment:
+ # TODO: in future we will migrate devnet to chain_id=1
+ # need to update this line in that case
+ RPC_URL_15658733: http://devnet:8545
+ ENSINDEXER_SCHEMA_NAME: docker_devnet_v1
+ depends_on:
+ postgres:
+ condition: service_healthy
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.devnet
+ required: true
+ - path: envs/.env.docker.local
+ required: false
+
+ ensrainbow:
+ extends:
+ file: services/ensrainbow.yml
+ service: ensrainbow
+ environment:
+ LABEL_SET_ID: ens-test-env
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.devnet
+ required: true
+ - path: envs/.env.docker.local
+ required: false
+
+ ensadmin:
+ extends:
+ file: services/ensadmin.yml
+ service: ensadmin
+ depends_on:
+ ensapi:
+ condition: service_started
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.devnet
+ required: true
+ - path: envs/.env.docker.local
+ required: false
+
+ postgres:
+ extends:
+ file: services/postgres.yml
+ service: postgres
+ env_file:
+ - path: ./envs/.env.docker.common
+ required: true
+
+ devnet:
+ extends:
+ file: services/devnet.yml
+ service: devnet
+
+volumes:
+ # Docker Compose requires volumes used by services to be declared in each
+ # compose file that references them — they cannot be inherited via `extends`.
+ # Explicit `name:` prevents collision with the base stack's volumes when both
+ # are run from the same directory (same project name).
+ postgres_data:
+ name: ensnode_devnet_postgres_data
+ driver: local
+ ensrainbow_data:
+ name: ensnode_devnet_ensrainbow_data
+ driver: local
diff --git a/docker/docker-compose.orchestrator.yml b/docker/docker-compose.orchestrator.yml
new file mode 100644
index 000000000..e813e27f6
--- /dev/null
+++ b/docker/docker-compose.orchestrator.yml
@@ -0,0 +1,22 @@
+# Minimal compose for CI integration tests.
+# Provides only the infrastructure services needed by orchestrator.ts:
+# devnet (local EVM) and postgres (database).
+services:
+ devnet:
+ extends:
+ file: services/devnet.yml
+ service: devnet
+
+ postgres:
+ extends:
+ file: services/postgres.yml
+ service: postgres
+ env_file:
+ - path: ./envs/.env.docker.common
+ required: true
+
+volumes:
+ # Docker Compose requires volumes used by services to be declared in each
+ # compose file that references them — they cannot be inherited via `extends`.
+ postgres_data:
+ driver: local
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 000000000..32bddab5d
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,72 @@
+services:
+ ensindexer:
+ extends:
+ file: services/ensindexer.yml
+ service: ensindexer
+ environment:
+ ENSINDEXER_SCHEMA_NAME: docker_ensindexer_v1
+ depends_on:
+ ensrainbow:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ # Copy envs/.env.docker.example to envs/.env.docker.local and configure
+ - path: envs/.env.docker.local
+ required: true
+
+ ensapi:
+ extends:
+ file: services/ensapi.yml
+ service: ensapi
+ environment:
+ ENSINDEXER_SCHEMA_NAME: docker_ensindexer_v1
+ depends_on:
+ postgres:
+ condition: service_healthy
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.local
+ required: true
+
+ ensrainbow:
+ extends:
+ file: services/ensrainbow.yml
+ service: ensrainbow
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.local
+ required: true
+
+ ensadmin:
+ extends:
+ file: services/ensadmin.yml
+ service: ensadmin
+ depends_on:
+ ensapi:
+ condition: service_started
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+ - path: envs/.env.docker.local
+ required: true
+
+ postgres:
+ extends:
+ file: services/postgres.yml
+ service: postgres
+ env_file:
+ - path: envs/.env.docker.common
+ required: true
+
+volumes:
+ # Docker Compose requires volumes used by services to be declared in each
+ # compose file that references them — they cannot be inherited via `extends`.
+ postgres_data:
+ driver: local
+ ensrainbow_data:
+ driver: local
diff --git a/docker/envs/.env.docker.common b/docker/envs/.env.docker.common
new file mode 100644
index 000000000..d8c682539
--- /dev/null
+++ b/docker/envs/.env.docker.common
@@ -0,0 +1,16 @@
+# Shared Docker Compose environment variables
+# Used by docker/docker-compose.yml and its variants
+
+
+# Postgres
+POSTGRES_DB=postgres
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=password
+
+# Internal service URLs (container-to-container)
+ENSDB_URL=postgresql://postgres:password@postgres:5432/postgres
+ENSRAINBOW_URL=http://ensrainbow:3223
+
+# ENS Admin
+ENSADMIN_PUBLIC_URL=http://localhost:4173
+NEXT_PUBLIC_SERVER_CONNECTION_LIBRARY=http://localhost:4334
diff --git a/docker/envs/.env.docker.devnet b/docker/envs/.env.docker.devnet
new file mode 100644
index 000000000..7c63beef8
--- /dev/null
+++ b/docker/envs/.env.docker.devnet
@@ -0,0 +1,6 @@
+# Default configuration for devnet docker-compose stack.
+# These values work out of the box — override by creating .env.docker.local.
+
+PLUGINS=subgraph,ensv2
+LABEL_SET_VERSION=0
+DB_SCHEMA_VERSION=3
diff --git a/docker/envs/.env.docker.example b/docker/envs/.env.docker.example
new file mode 100644
index 000000000..fb1b60416
--- /dev/null
+++ b/docker/envs/.env.docker.example
@@ -0,0 +1,41 @@
+# Docker environment configuration for ENSNode.
+# Copy this file to .env.docker.local and fill in the values.
+#
+# For devnet: env.docker.local is not required — defaults are in .env.docker.devnet.
+# Override by creating .env.docker.local with your custom values.
+# For mainnet/sepolia: copy this file to .env.docker.local and configure below.
+
+# ENS Namespace
+# Required. Must be: mainnet, sepolia, or ens-test-env.
+NAMESPACE=mainnet
+
+# Plugin Configuration
+# Required. Comma-separated list of plugins to activate.
+# For subgraph-compatible indexing only: PLUGINS=subgraph
+# For full indexing: PLUGINS=subgraph,basenames,lineanames,threedns,protocol-acceleration,registrars,tokenscope
+PLUGINS=subgraph
+
+# ENSRainbow Label Set Configuration
+# See https://ensnode.io/ensrainbow/usage/available-label-sets
+LABEL_SET_ID=subgraph
+LABEL_SET_VERSION=0
+DB_SCHEMA_VERSION=3
+
+# RPC Configuration
+# Required for mainnet/sepolia. Not needed for devnet.
+# You must use private (paid) RPC endpoints — public endpoints are too slow.
+#
+# Option 1: Auto-generate RPC URLs via provider API keys
+# ALCHEMY_API_KEY=
+# QUICKNODE_API_KEY=
+# QUICKNODE_ENDPOINT_NAME=
+# DRPC_API_KEY=
+#
+# Option 2: Explicit per-chain RPC URLs (takes precedence over auto-generated)
+# See apps/ensindexer/.env.local.example for the full list of chain IDs.
+# RPC_URL_1=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
+# RPC_URL_10=
+# RPC_URL_8453=
+# RPC_URL_42161=
+# RPC_URL_59144=
+# RPC_URL_534352=
diff --git a/docker/services/devnet.yml b/docker/services/devnet.yml
new file mode 100644
index 000000000..ba16e19b4
--- /dev/null
+++ b/docker/services/devnet.yml
@@ -0,0 +1,17 @@
+services:
+ devnet:
+ container_name: devnet
+ image: ghcr.io/ensdomains/contracts-v2:main-e8696c6
+ command: ./script/runDevnet.ts --testNames
+ pull_policy: always
+ ports:
+ - "8545:8545"
+ environment:
+ ANVIL_IP_ADDR: "0.0.0.0"
+ healthcheck:
+ test: ["CMD", "curl", "--fail", "-s", "http://localhost:8000/health"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+ start_interval: 1s
diff --git a/docker/services/ensadmin.yml b/docker/services/ensadmin.yml
new file mode 100644
index 000000000..fbc0fd542
--- /dev/null
+++ b/docker/services/ensadmin.yml
@@ -0,0 +1,9 @@
+services:
+ ensadmin:
+ container_name: ensadmin
+ image: ghcr.io/namehash/ensnode/ensadmin:latest
+ build:
+ dockerfile: ./apps/ensadmin/Dockerfile
+ context: ../..
+ ports:
+ - "4173:4173"
diff --git a/docker/services/ensapi.yml b/docker/services/ensapi.yml
new file mode 100644
index 000000000..71ce5c7d3
--- /dev/null
+++ b/docker/services/ensapi.yml
@@ -0,0 +1,16 @@
+services:
+ ensapi:
+ container_name: ensapi
+ image: ghcr.io/namehash/ensnode/ensapi:latest
+ build:
+ dockerfile: ./apps/ensapi/Dockerfile
+ context: ../..
+ ports:
+ - "4334:4334"
+ healthcheck:
+ test: ["CMD", "curl", "--fail", "-s", "http://localhost:4334/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 1m
+ start_interval: 1s
diff --git a/docker/services/ensindexer.yml b/docker/services/ensindexer.yml
new file mode 100644
index 000000000..ac472ddaf
--- /dev/null
+++ b/docker/services/ensindexer.yml
@@ -0,0 +1,16 @@
+services:
+ ensindexer:
+ container_name: ensindexer
+ image: ghcr.io/namehash/ensnode/ensindexer:latest
+ build:
+ dockerfile: ./apps/ensindexer/Dockerfile
+ context: ../..
+ ports:
+ - "42069:42069"
+ healthcheck:
+ test: ["CMD", "curl", "--fail", "-s", "http://localhost:42069/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 5m
+ start_interval: 1s
diff --git a/docker/services/ensrainbow.yml b/docker/services/ensrainbow.yml
new file mode 100644
index 000000000..5e1c3c726
--- /dev/null
+++ b/docker/services/ensrainbow.yml
@@ -0,0 +1,19 @@
+services:
+ ensrainbow:
+ container_name: ensrainbow
+ image: ghcr.io/namehash/ensnode/ensrainbow:latest
+ build:
+ dockerfile: ./apps/ensrainbow/Dockerfile
+ context: ../..
+ ports:
+ - "3223:3223"
+ volumes:
+ - ensrainbow_data:/app/apps/ensrainbow/data
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:3223/health"]
+ interval: 30s
+ timeout: 3s
+ retries: 3
+ start_period: 35m
+ start_interval: 1s
diff --git a/docker/services/postgres.yml b/docker/services/postgres.yml
new file mode 100644
index 000000000..85f0f12b2
--- /dev/null
+++ b/docker/services/postgres.yml
@@ -0,0 +1,14 @@
+services:
+ postgres:
+ container_name: postgres
+ image: postgres:17
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ start_period: 10s
diff --git a/docs/ensnode.io/ensapi-openapi.json b/docs/ensnode.io/ensapi-openapi.json
index e4496ea1a..2e27b8e27 100644
--- a/docs/ensnode.io/ensapi-openapi.json
+++ b/docs/ensnode.io/ensapi-openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "ENSApi APIs",
- "version": "1.9.0",
+ "version": "1.10.0",
"description": "APIs for ENS resolution, navigating the ENS nameforest, and metadata about an ENSNode"
},
"servers": [
diff --git a/docs/ensnode.io/src/content/docs/docs/contributing/index.mdx b/docs/ensnode.io/src/content/docs/docs/contributing/index.mdx
index fce21e875..36f271bf2 100644
--- a/docs/ensnode.io/src/content/docs/docs/contributing/index.mdx
+++ b/docs/ensnode.io/src/content/docs/docs/contributing/index.mdx
@@ -40,12 +40,12 @@ pnpm install
## Running ENSNode
:::note
-ENSNode is a suite of services, and some depend on others. Refer to the `docker-compose.yml` at the root of the monorepo for a full spec on the relationship between services.
+ENSNode is a suite of services, and some depend on others. Refer to the `docker/docker-compose.yml` in the docker directory for a full spec on the relationship between services.
:::
@@ -132,32 +132,46 @@ Before you can use Docker Compose, ensure you have the following installed on yo
### Building the Docker Images
-Before running `docker compose` the images must be build with the latest changes: see the [Building Docker Images](/ensnode/contributing/building) guide.
+Before running `docker compose` the images must be built with the latest changes: see the [Building Docker Images](/ensnode/contributing/building) guide.
If you make changes in the application code and wish to run those updates, you must build the relevant Docker container again.
### Running the Applications
-Run the built images with:
+For local development, use the devnet stack — no environment setup required:
```bash
-docker compose up -d
+docker compose -f docker/docker-compose.devnet.yml up -d
+```
+
+For mainnet/sepolia, first configure your environment:
+
+```bash
+cp docker/envs/.env.docker.example docker/envs/.env.docker.local
+```
+
+Edit `docker/envs/.env.docker.local` to set `NAMESPACE`, `PLUGINS`, and your RPC endpoints, then run:
+
+```bash
+docker compose -f docker/docker-compose.yml up -d
```
- **ENSIndexer**: Available at [http://localhost:42069](http://localhost:42069)
-- **ENSApi**: Available at [http://localhost:42069](http://localhost:4334)
+- **ENSApi**: Available at [http://localhost:4334](http://localhost:4334)
- **ENSRainbow**: Available at [http://localhost:3223](http://localhost:3223)
- **ENSAdmin**: Available at [http://localhost:4173](http://localhost:4173)
- **PostgreSQL**: Available on port `5432`
+For all available commands and configuration options, see the [Deploying with Docker](/ensnode/deploying/docker) guide and [`docker/README.md`](https://github.com/namehash/ensnode/blob/main/docker/README.md).
+
### Stopping the Applications
-To stop the running applications, you can press `Ctrl + C` in the terminal where Docker Compose is running. If you want to remove the containers and networks created by Docker Compose, you can run:
+To stop the running applications, you can press `Ctrl + C` in the terminal where Docker Compose is running. To remove the containers and networks:
```bash
-docker compose down
+docker compose -f docker/docker-compose.yml down
```
-:::note[Postgres Volume in this `docker-compose.yml`]
-`docker compoe down` will _not_ delete the Postgres data volume defined here, as it is a **named** volume. To fully delete the Postgres data volume and start from scratch, use `docker compose down -v`.
+:::note[Postgres Volume]
+`docker compose down` will _not_ delete the Postgres data volume, as it is a **named** volume. To fully delete it and start from scratch, use `docker compose -f docker/docker-compose.yml down -v`.
:::
diff --git a/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx b/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx
index 88f159aa3..46196b1e2 100644
--- a/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx
+++ b/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx
@@ -6,8 +6,6 @@ sidebar:
---
import { LinkCard } from '@astrojs/starlight/components';
-import { Code } from '@astrojs/starlight/components';
-import dockercompose from '@workspace/docker-compose.yml?raw';
The Docker images are the easiest way to run or deploy the ENSNode suite of services, both locally and in the cloud.
@@ -20,16 +18,49 @@ ENSIndexer runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` at startup to back part
href="/ensindexer/usage/configuration"
/>
-Below is a sample [Docker Compose](https://docs.docker.com/compose/) file linking the various services together.
+ENSNode provides several [Docker Compose](https://docs.docker.com/compose/) files for different use cases:
-
+- **`docker/docker-compose.yml`** — base stack for mainnet/sepolia: ensindexer, ensapi, ensrainbow, ensadmin, postgres
+- **`docker/docker-compose.devnet.yml`** — full stack against local devnet (ens-test-env), works out of the box with no configuration required
+
+### Mainnet / Sepolia
+
+Copy the example env file and configure it:
+
+```bash
+cp docker/envs/.env.docker.example docker/envs/.env.docker.local
+```
+
+Edit `docker/envs/.env.docker.local` to set your `NAMESPACE`, `PLUGINS`, and RPC endpoints (e.g. `ALCHEMY_API_KEY` or `RPC_URL_1`), then run:
+
+```bash
+docker compose -f docker/docker-compose.yml up -d
+```
+
+
+### Local devnet
+
+Configuration is optional. To customize defaults (e.g. change `PLUGINS`), copy the example as in previous step and edit it.
+
+Otherwise, skip setup and run directly:
+
+```bash
+docker compose -f docker/docker-compose.devnet.yml up -d
+```
+
+
+
:::note[Port Mappings]
-Note that while this example `docker-compose.yml` exposes each container's port to the host machine, useful for development, you may only wish to expose ENSApi's `4334` and avoid exposing services like ENSRainbow, ENSAdmin, and your Postgres to the wider internet.
+Note that while the default `docker/docker-compose.yml` exposes each container's port to the host machine (useful for development), you may only wish to expose ENSApi's `4334` and avoid exposing services like ENSRainbow, ENSAdmin, and Postgres to the wider internet.
:::
diff --git a/package.json b/package.json
index bacf366d6..67fe706bc 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"changeset-publish": "changeset publish",
"changeset-publish:next": "changeset publish --no-git-tag --snapshot --tag next",
"packages:prepublish": "pnpm -r prepublish",
- "devnet": "docker compose up devnet",
+ "devnet": "docker compose -f docker/docker-compose.devnet.yml up devnet",
"docker:build:ensnode": "pnpm run -w --parallel \"/^docker:build:.*/\"",
"docker:build:ensindexer": "docker build -f apps/ensindexer/Dockerfile -t ghcr.io/namehash/ensnode/ensindexer:latest .",
"docker:build:ensadmin": "docker build -f apps/ensadmin/Dockerfile -t ghcr.io/namehash/ensnode/ensadmin:latest .",
diff --git a/packages/integration-test-env/README.md b/packages/integration-test-env/README.md
index 92de243d9..2d90daf5b 100644
--- a/packages/integration-test-env/README.md
+++ b/packages/integration-test-env/README.md
@@ -10,7 +10,7 @@ The current devnet image is pinned to:
ghcr.io/ensdomains/contracts-v2:main-e8696c6
```
-via the `docker-compose.yml` at the monorepo root.
+via the `docker/docker-compose.orchestrator.yml` file.
## How It Works
@@ -40,7 +40,7 @@ When developing, it's useful to run each service individually so you can restart
#### 1. Start the devnet
```sh
-docker compose up devnet
+pnpm devnet
```
Runs the ENS contracts-v2 devnet on port 8545.
@@ -56,7 +56,7 @@ brew services start postgresql@17
or with the local docker compose:
```sh
-docker compose up postgres
+docker compose -f docker/docker-compose.yml up postgres
```
#### 3. Start ENSRainbow
@@ -64,7 +64,7 @@ docker compose up postgres
Run via docker compose:
```sh
-docker compose up ensrainbow
+docker compose -f docker/docker-compose.yml up ensrainbow
```
Or run it on the host machine from the repo root:
diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts
index 99df9f5f4..17e2a72d5 100644
--- a/packages/integration-test-env/src/orchestrator.ts
+++ b/packages/integration-test-env/src/orchestrator.ts
@@ -12,7 +12,7 @@
* 5. Run `pnpm test:integration` at the monorepo root
*
* Design decisions:
- * - Postgres and devnet are started from the root docker-compose.yml via
+ * - Postgres and devnet are started from docker/docker-compose.orchestrator.yml via
* testcontainers DockerComposeEnvironment, ensuring the orchestrator always
* uses the same images and configuration defined there.
* - execa for child process management — automatic cleanup on parent exit,
@@ -42,6 +42,7 @@ import { ENSNamespaceIds } from "@ensnode/datasources";
import { OmnichainIndexingStatusIds } from "@ensnode/ensnode-sdk";
const MONOREPO_ROOT = resolve(import.meta.dirname, "../../..");
+const DOCKER_DIR = resolve(MONOREPO_ROOT, "docker");
const ENSRAINBOW_DIR = resolve(MONOREPO_ROOT, "apps/ensrainbow");
const ENSINDEXER_DIR = resolve(MONOREPO_ROOT, "apps/ensindexer");
const ENSAPI_DIR = resolve(MONOREPO_ROOT, "apps/ensapi");
@@ -238,7 +239,10 @@ async function main() {
// Phase 1: Start Postgres + Devnet via docker-compose
log("Starting Postgres and devnet...");
- composeEnvironment = await new DockerComposeEnvironment(MONOREPO_ROOT, "docker-compose.yml")
+ composeEnvironment = await new DockerComposeEnvironment(
+ DOCKER_DIR,
+ "docker-compose.orchestrator.yml",
+ )
.withWaitStrategy("devnet", Wait.forHealthCheck())
.withWaitStrategy("postgres", Wait.forListeningPorts())
.withStartupTimeout(120_000)