# Local development quickstart

> Quickstart: Bring up local services with just recipes, shared Compose resources, database initialization, and expected health signals.

- Repository: macro-inc/macro
- GitHub: https://github.com/macro-inc/macro
- Human docs: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e
- Complete Markdown: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e/llms-full.txt

## Source Files

- `RUNNING_LOCALLY.md`
- `justfile`
- `docker-compose.yml`
- `docker-compose-databases.yml`
- `local_stack.just`
- `infra/stacks/fusionauth-instance/README.md`

---

---
title: "Local development quickstart"
description: "Quickstart: Bring up local services with just recipes, shared Compose resources, database initialization, and expected health signals."
---

Local development is orchestrated from the root `justfile`: `just setup` prepares `.env`, shared Docker resources, LocalStack, Postgres migrations, FusionAuth, and cached Rust service images; `just run_local` starts the Docker Compose service stack under the fixed `macro` Compose project.

## Prerequisites

Install the tools expected by `RUNNING_LOCALLY.md` and the recipes:

| Tool | Used for |
| --- | --- |
| Docker / Docker Compose | Databases, LocalStack, FusionAuth, backend services |
| `just` | Repository task runner |
| `sops` | Decrypting `.env-local*.enc` into `.env` |
| AWS CLI | Creating LocalStack SQS, DynamoDB, and S3 resources |
| SQLx CLI | Creating and migrating `macrodb` |
| Pulumi | Creating the local FusionAuth stack |
| Bun and Node | Frontend, FusionAuth stack dependencies, Playwright |

If not using the repository Nix shell, export the configured SOPS KMS ARN before decrypting local environment files.

```bash
export SOPS_KMS_ARN="arn:aws:kms:us-east-1:569036502058:key/mrk-cab29bf948044eb79005a81f48d40e93,arn:aws:kms:us-west-1:569036502058:key/mrk-cab29bf948044eb79005a81f48d40e93"
```

<Warning>
The local Docker resources are intentionally frozen to the `macro` Compose project. Multiple checkouts and worktrees share the same containers, networks, and volumes. Do not run two local stacks at the same time.
</Warning>

## Quickstart

<Steps>
  <Step title="Initialize local dependencies">
    Run the repository setup recipe from the repository root.

    ```bash
    just setup
    ```

    This decrypts `.env`, creates shared Docker networks and volumes, initializes LocalStack, creates and migrates the local database, configures FusionAuth, and prebuilds development service images.
  </Step>

  <Step title="Start backend services">
    Start the Compose stack.

    ```bash
    just run_local
    ```

    To rebuild service containers after local service changes, pass the build flag.

    ```bash
    just run_local --build
    ```
  </Step>

  <Step title="Start the frontend">
    In a second terminal, run the frontend against local services.

    ```bash
    cd js/app
    bun i
    just local
    ```

    `just local` runs the app with `VITE_LOCAL_SERVERS=ALL` and defaults to `PORT=3000`.
  </Step>
</Steps>

## Shared Docker resources

`create_networks` creates the shared resources used across the root Compose file, the database Compose file, and the FusionAuth Compose file.

| Resource | Name |
| --- | --- |
| Compose project | `macro` |
| External networks | `databases`, `auth` |
| Compose service network | `services` |
| Postgres volume | `macro_postgres_data` |
| Redis volume | `macro_redis_data` |
| OpenSearch volume | `macro_opensearch_data` |
| FusionAuth database volume | `fusionauth_db_data` |
| FusionAuth config volume | `fusionauth_config` |

The root `docker-compose.yml` includes `docker-compose-databases.yml` and `infra/stacks/fusionauth-instance/docker-compose.yml`, so `just run_local` can start application services together with Postgres, Redis, OpenSearch, and FusionAuth.

## What `just setup` initializes

| Stage | Recipe | Result |
| --- | --- | --- |
| Environment | `just get_environment` | Decrypts `.env-local.enc` into root `.env`; optionally patches AWS credentials from the default local AWS profile when an environment suffix is used |
| Docker resources | `just create_networks` | Creates shared networks and volumes |
| Local AWS | `just setup_localstack` | Starts `localstack/localstack:4` on port `4566`; creates SQS queues, DynamoDB tables, S3 buckets, bucket CORS, and the document upload finalizer notification |
| Local database | `just setup_local_dbs` | Starts Postgres and Redis, creates `macrodb`, runs migrations, then stops the database Compose services |
| FusionAuth | `infra/stacks/fusionauth-instance/setup` | Starts FusionAuth, waits for `/api/status`, applies the Pulumi `local` stack, patches root `.env`, then stops FusionAuth |
| Service image cache | `rust/cloud-storage/build_dev_service_images` | Builds `macro-local-rust-services:dev` and the search-processing image |

The local Macro database URL used by the Rust database recipes is:

```text
postgres://user:password@localhost:5432/macrodb
```

## LocalStack resources

`local_stack.just` starts LocalStack as a standalone Docker container named `localstack` on the shared `databases` network.

| Type | Local resources |
| --- | --- |
| Services | `sqs`, `dynamodb`, `s3` |
| Endpoint | `http://localhost:4566` |
| Buckets | `macro-email-attachments`, `doc-storage`, `docx-upload`, `static-file-storage`, `bulk-upload-staging` |
| DynamoDB tables | `bulk-upload`, `connection-gateway-table`, `static-file-metadata` |
| Document upload event | S3 `ObjectCreated` events from `doc-storage` are wired to `document-upload-finalizer-queue` |

All buckets receive a local CORS policy for `http://localhost:3000` through `http://localhost:3009`.

## Service ports and health signals

Most Rust services expose container port `8080` and map it to a distinct host port.

| Service | Host port | Health check |
| --- | ---: | --- |
| `authentication-service` | `8080` | `GET /health` |
| `connection_gateway` | `8082` | `GET /health` |
| `contacts_service` | `8083` | `GET /health` |
| `document_cognition_service` | `8085` | `GET /health` |
| `document_storage_service` | `8086` | `GET /health` |
| `email_service` | `8087` | `GET /health` |
| `notification_service` | `8089` | `GET /health` |
| `search_processing_service` | `8092` | `GET /health`; behind the `processors` profile |
| `static_file_service` | `8094` | `GET /api/health` |
| `static_file_cdn` | `8100` | Nginx proxy to S3/static file service |
| `unfurl_service` | `8095` | `GET /health` |
| `image_proxy_service` | `8097` | `GET /health` |
| `lexical_service` | `8096` | `GET /health` |
| `sync_service` | `8787` | `GET /health` |
| `websocket_service` | `6969` | WebSocket endpoint; no Compose health check |
| FusionAuth | `9011` | `GET /api/status` returns `{"status":"Ok"}` |
| LocalStack | `4566` | `GET /_localstack/health` |
| Postgres | `5432` | `pg_isready -U user` |
| Redis | `6379`, UI on `8001` | Container availability |
| OpenSearch | `9200`, analyzer on `9600` | `GET /` |

Use Docker Compose waiting when starting a subset or detached stack:

```bash
just run_local -d --wait
```

## Frontend local service selection

`js/app/justfile` provides local frontend shortcuts. The default local command selects all local service hosts.

```bash
cd js/app
just local
```

The underlying app config reads `VITE_LOCAL_SERVERS`:

| Value | Behavior |
| --- | --- |
| unset | Uses remote development service hosts in development mode |
| `ALL` | Uses all local service hosts |
| comma-separated service names | Uses local hosts only for the selected services |
| `service-name:port` | Uses a local host with a port override for that service |

List recognized local service names with:

```bash
cd js/app
just local-services
```

## Local E2E smoke path

The local smoke harness runs the backend with local-only service overrides, seeds deterministic fixtures, starts the frontend with local bearer-token auth, and runs Playwright.

```bash
just local-e2e
```

Related harnesses:

```bash
just local-e2e-rust
just local-e2e-all
just local-e2e-ui
```

The E2E path uses `docker-compose.local-e2e.yml` to override database, Redis, LocalStack, bucket, table, and queue environment variables so the smoke suite does not mutate shared dev assets. The seed command is guarded by `LOCAL_E2E_SEED=true` and rejects any `DATABASE_URL` outside the local Docker database shape:

```text
postgres://user:...@(localhost|127.0.0.1|postgres):5432/macrodb
```

## Stopping and resetting

| Command | Effect |
| --- | --- |
| `just stop-local` | Runs `docker compose down` for the root stack |
| `just stop-databases` | Stops the database Compose file |
| `just stop_fusionauth` | Stops the FusionAuth Compose file |
| `just destroy` | Destroys the local FusionAuth Pulumi stack and runs Compose down with volumes for the root stack |
| `just docker_cache_clear` | Clears all BuildKit build cache |
| `just docker_cache_clear_targets` | Clears Rust target cache mounts only |

## Troubleshooting

### `.env not found`

`just run_local` patches local FusionAuth values into `.env`. If `.env` is missing, run:

```bash
just get_environment
```

For a fresh checkout, prefer the full setup:

```bash
just setup
```

### FusionAuth local stack not found

If `patch_local_fusionauth_env` reports that the Pulumi local stack is missing, run:

```bash
just setup
```

FusionAuth local admin defaults are:

```text
username: admin@macro.com
password: macroIsGreat!
api-key: bf69486b-4733-4954-a44e-2e1b5f2c8a91
```

### Local E2E token generation fails

Prefer the repository-level harness:

```bash
just local-e2e
```

If running Playwright directly, seed first and ensure `.env` exists:

```bash
just local-e2e-seed
cd js/app
LOCAL_E2E=true bunx playwright test
```

You can bypass token generation by exporting `LOCAL_JWT`.

### Search processing on Apple Silicon

`search_processing_service` is pinned to `linux/amd64` because its local `pdfium` library is AMD64-only. On Apple Silicon, enabling the processor profile uses QEMU emulation for build and runtime.

## Related pages

<CardGroup>
  <Card title="Frontend local service selection" href="/frontend-local-services">
    How `VITE_LOCAL_SERVERS` maps frontend clients to local or remote service hosts.
  </Card>
  <Card title="Local E2E smoke testing" href="/local-e2e-smoke-testing">
    Deterministic seed data, Playwright auth, and Rust local E2E integration tests.
  </Card>
</CardGroup>
