diff --git a/.dockerignore b/.dockerignore index 966e0fa7..41041fa9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,24 +1,24 @@ -**/node_modules -.git -.github -.husky -payment_router -*.md -.env -**/.env -*.db - -# Backend test files & artifacts — never needed inside an image -# (CI runs tests on the runner; docker-compose.test.yml builds from the -# stellar-payment-platform context, which has its own .dockerignore) -stellar-payment-platform/tests -stellar-payment-platform/*.test.js -stellar-payment-platform/test_*.txt -stellar-payment-platform/test_output.txt -stellar-payment-platform/data -stellar-payment-platform/artillery.yml - -# Root-level files not referenced by any image build -docs -tmp_server.js +**/node_modules +.git +.github +.husky +payment_router +*.md +.env +**/.env +*.db + +# Backend test files & artifacts — never needed inside an image +# (CI runs tests on the runner; the api-test service builds from the +# stellar-payment-platform context, which has its own .dockerignore) +stellar-payment-platform/tests +stellar-payment-platform/*.test.js +stellar-payment-platform/test_*.txt +stellar-payment-platform/test_output.txt +stellar-payment-platform/data +stellar-payment-platform/artillery.yml + +# Root-level files not referenced by any image build +docs +tmp_server.js artillery.yml \ No newline at end of file diff --git a/README.md b/README.md index 758122f6..817c187c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,47 @@ The following diagram maps exactly how data flows between the user, Render, and > These steps are split by module so you can run only what you need. +### Docker Compose profiles + +Every service in `docker-compose.yml` belongs to a profile, so +`docker compose up` on its own starts nothing and you always say which stack +you want: + +| Command | Starts | +| --- | --- | +| `docker compose --profile dev up` | backend, postgres, redis | +| `docker compose --profile full up` | the same, plus the built frontend on :3000 | +| `docker compose --profile test up` | the API under test on :5001 and its own database | +| `docker compose --profile integration up` | a local standalone Stellar network on :8000 | + +`dev` is the everyday one. `full` adds the frontend, which is the slowest +thing in the file to build and is not needed for backend work. + +The dev and test stacks use separate databases on separate host ports (5432 +and 5433), so you can run both at once: + +```bash +docker compose --profile dev --profile test up -d +``` + +`COMPOSE_PROFILES` works too, if you would rather not repeat the flag: + +```bash +export COMPOSE_PROFILES=dev +docker compose up -d +``` + +Stop a stack with the same profile you started it with, otherwise Compose +will not know which services it is meant to remove: + +```bash +docker compose --profile dev down +``` + +The `integration` profile pulls `stellar/quickstart`, a multi-gigabyte image +used only by the Soroban contract tests in `tests/integration/`. It is kept +out of `test` so the API tests do not drag it in. + ### Frontend dashboard ```bash @@ -111,7 +152,14 @@ For a typical local install that becomes, for example: DATABASE_URL="postgresql://postgres:postgres@localhost:5432/stellar_tags?schema=public" ``` -The quickest way to get a local database is Docker: +The quickest way to get a local database is the dev profile, which also +brings up Redis: + +```bash +docker compose --profile dev up -d postgres redis +``` + +Or a single container, if you want nothing else: ```bash docker run --name stellar-postgres -e POSTGRES_PASSWORD=postgres \ diff --git a/docker-compose.test.yml b/docker-compose.test.yml deleted file mode 100644 index 2b86e8c3..00000000 --- a/docker-compose.test.yml +++ /dev/null @@ -1,64 +0,0 @@ -services: - postgres-test: - image: postgres:16-alpine - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: stellar_tags_test - ports: - - "5433:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s - timeout: 3s - retries: 5 - - api-test: - build: - context: ./stellar-payment-platform - dockerfile: Dockerfile.test - environment: - PORT: "5001" - DATABASE_URL: postgresql://postgres:postgres@postgres-test:5432/stellar_tags_test?schema=public - ports: - - "5001:5001" - depends_on: - postgres-test: - condition: service_healthy - networks: - - stellar-test-net - healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:5001/health"] - interval: 5s - timeout: 3s - retries: 5 - start_period: 10s - - # Standalone Stellar network (Soroban-enabled) used by the contract - # integration tests. Exposes both the Horizon REST API (8000) and the - # Soroban RPC endpoint (8000/rpc) so the Rust SDK can submit real - # transactions against a local network. - stellar-standalone: - image: stellar/quickstart:latest - command: [ - "--standalone", - "--enable-soroban-rpc", - "--enable-core-artifacts", - "--local", - ] - environment: - ENABLE_LOGS: "true" - ports: - - "8000:8000" - networks: - - stellar-test-net - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8000/"] - interval: 5s - timeout: 5s - retries: 20 - start_period: 30s - -networks: - stellar-test-net: - driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 104fb311..04b7c3a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,64 +1,146 @@ -services: - postgres: - image: postgres:16-alpine - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: stellar_tags - ports: - - "5432:5432" - volumes: - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s - timeout: 3s - retries: 5 - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 3s - retries: 5 - - backend: - build: - context: . - dockerfile: Dockerfile - target: backend - ports: - - "5000:5000" - environment: - DATABASE_URL: postgresql://postgres:postgres@postgres:5432/stellar_tags?schema=public - REDIS_URL: redis://redis:6379 - PORT: "5000" - LOG_DIR: /app/logs - volumes: - # Keep rotated logs across container restarts. The transports cap the - # volume's growth by rotating daily / at 20MB and pruning old archives. - - backendlogs:/app/logs - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - - frontend: - build: - context: . - dockerfile: Dockerfile - target: frontend - args: - VITE_API_BASE: http://localhost:5000 - ports: - - "3000:80" - depends_on: - - backend - -volumes: - pgdata: - backendlogs: +# Every service belongs to a profile, so `docker compose up` on its own starts +# nothing and you always say which stack you want: +# +# docker compose --profile dev up backend + postgres + redis +# docker compose --profile full up the same, plus the built frontend +# docker compose --profile test up API under test + its own database +# docker compose --profile integration up local standalone Stellar network +# +# dev and test use separate databases on separate host ports, so both can run +# at once without colliding. + +services: + # ── dev ──────────────────────────────────────────────────────────────────── + postgres: + profiles: ["dev", "full"] + image: postgres:16-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: stellar_tags + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 3s + retries: 5 + + redis: + profiles: ["dev", "full"] + image: redis:7-alpine + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + backend: + profiles: ["dev", "full"] + build: + context: . + dockerfile: Dockerfile + target: backend + ports: + - "5000:5000" + environment: + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/stellar_tags?schema=public + REDIS_URL: redis://redis:6379 + PORT: "5000" + LOG_DIR: /app/logs + volumes: + # Keep rotated logs across container restarts. The transports cap the + # volume's growth by rotating daily / at 20MB and pruning old archives. + - backendlogs:/app/logs + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + # Only in full, not dev: the Vite build is the slowest thing here and backend + # work does not need it. full is dev plus this. + frontend: + profiles: ["full"] + build: + context: . + dockerfile: Dockerfile + target: frontend + args: + VITE_API_BASE: http://localhost:5000 + ports: + - "3000:80" + depends_on: + - backend + + # ── test ─────────────────────────────────────────────────────────────────── + postgres-test: + profiles: ["test"] + image: postgres:16-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: stellar_tags_test + # 5433 on the host so a dev postgres can stay up on 5432. + ports: + - "5433:5432" + # No volume: a test database should start empty every time. + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 3s + retries: 5 + + api-test: + profiles: ["test"] + build: + context: ./stellar-payment-platform + dockerfile: Dockerfile.test + environment: + PORT: "5001" + DATABASE_URL: postgresql://postgres:postgres@postgres-test:5432/stellar_tags_test?schema=public + ports: + - "5001:5001" + depends_on: + postgres-test: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:5001/health"] + interval: 5s + timeout: 3s + retries: 5 + start_period: 10s + + # ── integration ──────────────────────────────────────────────────────────── + # Standalone Stellar network (Soroban-enabled) used by the contract + # integration tests. Exposes both the Horizon REST API (8000) and the + # Soroban RPC endpoint (8000/rpc) so the Rust SDK can submit real + # transactions against a local network. Kept out of the test profile + # because it pulls a multi-gigabyte image the API tests never touch. + stellar-standalone: + profiles: ["integration"] + image: stellar/quickstart:latest + command: [ + "--standalone", + "--enable-soroban-rpc", + "--enable-core-artifacts", + "--local", + ] + environment: + ENABLE_LOGS: "true" + ports: + - "8000:8000" + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8000/"] + interval: 5s + timeout: 5s + retries: 20 + start_period: 30s + +volumes: + pgdata: + backendlogs: diff --git a/stellar-payment-platform/Dockerfile.test b/stellar-payment-platform/Dockerfile.test index b5d6b349..de514bcd 100644 --- a/stellar-payment-platform/Dockerfile.test +++ b/stellar-payment-platform/Dockerfile.test @@ -3,10 +3,14 @@ FROM node:20-alpine WORKDIR /app COPY package*.json ./ +# Before npm ci: the postinstall hook runs `prisma generate`, which fails +# unless the schema is already present. +COPY prisma ./prisma RUN npm ci --omit=dev COPY . . -EXPOSE 5000 +# Matches PORT in the api-test service and its healthcheck. +EXPOSE 5001 CMD ["node", "server.js"] diff --git a/tests/integration/README.md b/tests/integration/README.md index 9e092fac..6584101d 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -10,7 +10,7 @@ observe the resulting network state. ``` ┌─────────────────────────────────────────────────────────────┐ -│ docker-compose.test.yml │ +│ docker compose --profile integration │ │ │ │ stellar-standalone (stellar/quickstart) │ │ ├── Horizon REST API ──► http://localhost:8000 │ @@ -36,7 +36,7 @@ smart contracts out of the box. ```bash # 1. Start the standalone Stellar network -docker compose -f docker-compose.test.yml up -d stellar-standalone +docker compose --profile integration up -d stellar-standalone # 2. Wait for Horizon to be ready (healthcheck handles this automatically) diff --git a/tests/integration/deploy.sh b/tests/integration/deploy.sh index e578751c..f83566fa 100644 --- a/tests/integration/deploy.sh +++ b/tests/integration/deploy.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash # # Deploys the payment_router Soroban contract to the local standalone -# Stellar network started by docker-compose.test.yml. +# Stellar network started by docker-compose.yml's integration profile. # # Prerequisites: # - Docker running with the standalone network up: -# docker compose -f docker-compose.test.yml up -d stellar-standalone +# docker compose --profile integration up -d stellar-standalone # - The `soroban` CLI installed (https://soroban.stellar.org/docs/reference/cli) # - Rust toolchain with the `wasm32-unknown-unknown` target: # rustup target add wasm32-unknown-unknown diff --git a/tests/integration/run.sh b/tests/integration/run.sh index 402e8d55..19b5cfc7 100644 --- a/tests/integration/run.sh +++ b/tests/integration/run.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # # End-to-end integration test for the payment_router Soroban contract -# against a local standalone Stellar network (docker-compose.test.yml). +# against a local standalone Stellar network (the integration profile in +# docker-compose.yml). # # Verifies: # 1. The contract can be deployed to the local network. @@ -10,7 +11,7 @@ # fee and crediting the recipient, as observed through local Horizon. # # Prerequisites: -# - docker compose -f docker-compose.test.yml up -d stellar-standalone +# - docker compose --profile integration up -d stellar-standalone # - soroban CLI installed # - rustup target add wasm32-unknown-unknown #