This guide walks through setting up a local development environment, running the service, and deploying with Docker.
For an overview of the system design and module responsibilities, see architecture.md.
The fastest way to get started is the setup script, which handles prerequisites checking, environment configuration, Docker services, migrations, and a health check in one command:
./scripts/setup.shTo wipe all existing data and start completely fresh:
./scripts/setup.sh --resetThe script will:
- Verify Rust, Docker, and
psqlare installed - Copy
.env.example.failover→.envif no.envexists - Start PostgreSQL and Redis via Docker Compose
- Wait for both services to pass their health checks
- Install
sqlx-cliif needed and run all migrations - Print next steps to start the server
If you prefer to set things up manually, follow the steps below.
| Tool | Version | Installation |
|---|---|---|
| Rust | 1.84+ (stable) | rustup.rs |
| PostgreSQL | 14+ | Via Docker (recommended) or native install |
| Docker | 20+ | docker.com |
| sqlx-cli | 0.7+ | cargo install sqlx-cli |
git clone https://github.com/synapse-bridgez/synapse-core.git
cd synapse-coreCopy the example env file and customize:
cp .env.example .env| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
✅ | — | PostgreSQL connection string |
SERVER_PORT |
❌ | 3000 |
Port for the HTTP server |
STELLAR_HORIZON_URL |
✅ | — | Stellar Horizon API endpoint |
Example .env:
SERVER_PORT=3000
DATABASE_URL=postgres://synapse:synapse@localhost:5432/synapse
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.orgStart a PostgreSQL container:
docker run --name synapse-postgres \
-e POSTGRES_USER=synapse \
-e POSTGRES_PASSWORD=synapse \
-e POSTGRES_DB=synapse \
-p 5432:5432 \
-d postgres:14-alpineVerify it's running:
docker exec -it synapse-postgres pg_isready -U synapseIf you have PostgreSQL installed locally:
createuser synapse --password # enter "synapse" when prompted
createdb synapse --owner=synapseUpdate DATABASE_URL in .env to match your local credentials.
Migrations run automatically when the app starts. To run them manually:
cargo install sqlx-cli # if not already installed
sqlx migrate runThe migration file migrations/20250216000000_init.sql creates the transactions table and indexes. See architecture.md for the full schema.
cargo runYou should see output like:
INFO synapse_core > Database migrations completed
INFO synapse_core > listening on 0.0.0.0:3000
cargo build --release
./target/release/synapse-corecurl http://localhost:3000/health
# => OKdocker exec -it synapse-postgres psql -U synapse -c "CREATE DATABASE synapse_test;"DATABASE_URL=postgres://synapse:synapse@localhost:5432/synapse_test cargo testNote: Some warnings about unused imports or dead code are expected — they correspond to features planned for future issues.
Integration tests built on tests/common::TestApp (health checks, lifecycle, PITR restore, etc.)
need their own Postgres/Redis. TestApp::new() looks for infra in this order:
TEST_DATABASE_URL/TEST_REDIS_URL, if set — reuses whatever you already have running.- A fresh
testcontainersPostgres, if Docker is reachable (Redis is not auto-started this way — see below). - Otherwise it fails fast with a message naming exactly what's missing and how to fix it.
For iterative local runs, start the stack once and point tests at it instead of paying the testcontainer startup cost (container create + Postgres boot) on every test binary:
docker compose up -d postgres redis
TEST_DATABASE_URL=postgres://synapse:synapse@localhost:5432/synapse \
TEST_REDIS_URL=redis://localhost:6379 \
cargo testThis makes a real difference: each #[tokio::test] in health_check_test.rs calls
TestApp::new() independently, so on a Docker-only fallback every test pays for its own Postgres
container. Measured locally, cargo test --test health_check_test went from ~61s (fresh
testcontainer per test) to ~9s (reusing docker compose up -d postgres redis). If you don't set
TEST_DATABASE_URL/TEST_REDIS_URL and don't have Docker running, tests fail fast with a message
naming exactly what's missing instead of a raw testcontainers or Docker-daemon error.
cargo fmt -- --check # check formatting
cargo fmt # auto-format
cargo clippy # lintSpin up both PostgreSQL and the application:
docker compose up --buildThis starts:
| Service | Container | Port | Description |
|---|---|---|---|
| postgres | synapse-postgres |
5432 | PostgreSQL 14 Alpine |
| app | synapse-app |
3000 | synapse-core server |
The app waits for PostgreSQL's health check to pass before starting. Migrations run automatically on boot.
To stop:
docker compose downTo also remove the database volume:
docker compose down -vThe dev compose file mounts source code as volumes and uses cargo-watch to rebuild on file changes.
docker compose -f docker-compose.dev.yml upThis starts:
| Service | Container | Port | Description |
|---|---|---|---|
| postgres | synapse-postgres-dev |
5432 | PostgreSQL 14 Alpine |
| redis | synapse-redis-dev |
6379 | Redis 7 Alpine |
| adminer | synapse-adminer |
8080 | Adminer database UI |
| app | synapse-app-dev |
3000 | synapse-core with hot-reload |
Hot reload: Any change to a file under src/ or migrations/ triggers an automatic recompile and restart via cargo-watch. The first startup is slow (compiling from scratch); subsequent reloads are fast.
Database UI: Open http://localhost:8080 in your browser. Use these credentials:
- System:
PostgreSQL - Server:
postgres - Username:
synapse - Password:
synapse - Database:
synapse
Debug port: Port 9229 is exposed for IDE debugger attachment (lldb-server / rust-gdb). Configure your IDE to connect to localhost:9229.
Cargo cache: The cargo_registry, cargo_git, and target_cache Docker volumes persist the build cache between restarts, so you only pay the full compile cost once.
To stop and remove dev containers:
docker compose -f docker-compose.dev.yml downTo also wipe the build cache volumes (forces a full recompile next time):
docker compose -f docker-compose.dev.yml down -vBuild the image manually:
docker build -t synapse-core .Run it (assumes PostgreSQL is reachable at the given URL):
docker run -p 3000:3000 \
-e DATABASE_URL=postgres://synapse:synapse@host.docker.internal:5432/synapse \
-e SERVER_PORT=3000 \
-e STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org \
synapse-coreNote: Use
host.docker.internal(macOS/Windows) or172.17.0.1(Linux) to reach the host machine's PostgreSQL from inside a container.
synapse-core/
├── Cargo.toml # Dependencies and workspace config
├── .env.example # Example environment variables
├── docker-compose.yml # Full-stack Docker setup
├── dockerfile # Multi-stage Rust build
├── migrations/
│ └── 20250216000000_init.sql # Initial schema migration
├── docs/
│ ├── architecture.md # System design & module docs
│ └── setup.md # This file
├── src/
│ ├── main.rs # Entry point, server setup, migrations
│ ├── config.rs # Environment variable configuration
│ ├── error.rs # Custom error types (planned)
│ ├── db/
│ │ ├── mod.rs # Connection pool creation
│ │ └── models.rs # Transaction struct & tests
│ ├── handlers/
│ │ ├── mod.rs # Health check handler
│ │ └── webhook.rs # Callback handler (planned)
│ ├── services/
│ │ ├── mod.rs # Service exports (planned)
│ │ └── transaction_processor.rs # Business logic (planned)
│ └── stellar/
│ ├── mod.rs # Stellar module exports (planned)
│ └── client.rs # Horizon API client (planned)
├── tests/
│ └── integration_test.rs # Integration tests (planned)
└── .github/
└── workflows/
└── rust.yml # CI pipeline
| Method | Path | Status | Description |
|---|---|---|---|
| GET | /health |
✅ Active | Health check — returns "OK" |
| POST | /callback/transaction |
🚧 Planned | Receive Stellar Anchor Platform webhooks |
| GET | /transactions |
🚧 Planned | List transactions with pagination |
| GET | /transactions/:id |
🚧 Planned | Get a single transaction by UUID |
| Problem | Solution |
|---|---|
DATABASE_URL must be set |
Ensure .env exists and DATABASE_URL is set |
Failed to connect to test DB |
Create the test DB: CREATE DATABASE synapse_test; |
SQLX_OFFLINE errors in CI |
Run cargo sqlx prepare locally to generate offline query data |
| Port already in use | Change SERVER_PORT in .env or stop the conflicting process |
| Docker connection refused to Postgres | Use host.docker.internal or 172.17.0.1 instead of localhost |