# How to Configure Macro for a Project: A Complete Setup Guide

> Learn how to configure Macro for your project using a single JSON file. Simplify your CI/CD setup and manage all services declaratively.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: how-to-guide
- Published: 2026-08-14

---

**Macro uses a single JSON file—[`.github/services-config.json`](https://github.com/macro-inc/macro/blob/main/.github/services-config.json)—to declaratively configure all services, Lambda handlers, and Pulumi stacks, with the rest of CI/CD deriving from this central source of truth.**

Macro is a **multi-service Rust monorepo** built around a minimal, discoverable configuration model. Rather than scattering settings across dozens of files, the project leans on one declarative manifest that drives builds, deployments, and secret management. This guide walks through exactly how to configure Macro for any new project, based on the actual implementation in `macro-inc/macro`.

## The Central Configuration: services-config.json

All service discovery in Macro flows through **[`.github/services-config.json`](https://github.com/macro-inc/macro/blob/main/.github/services-config.json)**. This file lives at the repository root and acts as the single source of truth for what services exist, where their code lives, and how they deploy.

Each service entry follows this structure:

```json
{
  "service-name": {
    "source_paths": [ "path/to/code/**" ],
    "stack_path": "infra/stacks/service-name/**",
    "deploy_binaries": [ "binary-name" ],
    "deploy_lambdas": [ "lambda-handler-name" ]
  }
}

```

The four fields control distinct phases of the pipeline:

- **`source_paths`** — Glob patterns that define which files belong to this service. The CI uses these to detect changes and trigger incremental rebuilds.
- **`stack_path`** — Points to the Pulumi stack definition for infrastructure as code deployments.
- **`deploy_binaries`** — Names of compiled Rust binaries to package and deploy (for long-running services like HTTP APIs).
- **`deploy_lambdas`** — Names of Lambda handler functions to build and upload to AWS.

The file is read by multiple components: [`detect-affected-services.sh`](https://github.com/macro-inc/macro/blob/main/detect-affected-services.sh) for change detection, [`get-project-name/get-project-name.sh`](https://github.com/macro-inc/macro/blob/main/get-project-name/get-project-name.sh) for stack resolution, and the `xtask_workflows` runner for orchestration.

## How CI/CD Discovers Your Configuration

Macro's CI pipeline automatically adapts to changes in [`services-config.json`](https://github.com/macro-inc/macro/blob/main/services-config.json). No manual registration steps are required.

### Change Detection

The script [`.github/scripts/detect-affected-services.sh`](https://github.com/macro-inc/macro/blob/main/.github/scripts/detect-affected-services.sh) parses the JSON file using `jq`, maps changed files from `git diff` against the `source_paths` globs, and outputs exactly which services need rebuilding. This enables **incremental builds** across a large monorepo—only touched services and their dependents recompile.

You can run this locally to preview CI behavior:

```bash
./.github/scripts/detect-affected-services.sh

```

Output shows detected services like:

```

Detected services: email-service, notification-service

```

### Pulumi Stack Resolution

When a workflow needs to deploy, the reusable action [`.github/actions/get-project-name/get-project-name.sh`](https://github.com/macro-inc/macro/blob/main/.github/actions/get-project-name/get-project-name.sh) extracts the `stack_path` for the target service. This keeps deployment logic generic: the action receives a service name, looks up its configuration, and invokes Pulumi against the correct stack.

## Adding a New Service to Macro

To configure a new project in Macro, edit [`.github/services-config.json`](https://github.com/macro-inc/macro/blob/main/.github/services-config.json) and add an entry following the established pattern:

```json
{
  "my-awesome-service": {
    "source_paths": [
      "services/my_awesome_service/**",
      "crates/shared_utils/**"
    ],
    "stack_path": "infra/stacks/my-awesome-service/**",
    "deploy_binaries": [ "my_awesome_service" ],
    "deploy_lambdas": [ "my_awesome_handler" ]
  }
}

```

Key design decisions to make:

1. **Source path completeness** — Include every directory that, if modified, should trigger a rebuild of this service. Shared crates referenced by multiple services should be listed in each dependent's `source_paths`.
2. **Binary vs. Lambda architecture** — Use `deploy_binaries` for persistent workloads (containerized APIs, workers) and `deploy_lambdas` for event-driven, serverless handlers. A service can define both.
3. **Stack path convention** — Mirror the service name in `infra/stacks/` for discoverability, though the JSON value can point anywhere.

After commit, the new service automatically participates in change detection and becomes available to the `just build` and deployment workflows.

## Secret Management with Doppler

Macro centralizes secrets in **Doppler** and validates configurations at build time. The `validate_doppler_configs` step—implemented in [`tooling/xtask/crates/xtask_workflows/src/workflows/code_check_cloud_storage.rs`](https://github.com/macro-inc/macro/blob/main/tooling/xtask/crates/xtask_workflows/src/workflows/code_check_cloud_storage.rs) (lines 197-209)—builds and runs tiny validation binaries to ensure every service's Doppler configuration exists and matches the expected schema.

This validation happens **before** any actual build or deployment, failing fast if secrets are misconfigured.

## Runtime Environment Variables

The codebase never calls `std::env::var` directly. Instead, all runtime configuration flows through the **`macro_env_var` crate**, located in [`crates/database_env_vars/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/database_env_vars/src/lib.rs).

Usage in application code:

```rust
macro_env_var::env!("POSTGRES_URL");

```

The macro expands to type-safe, consistent loading that handles both Doppler-provided secrets and local development values from `.env`. The repository includes `.env.example` as a template—copy this to `.env` and fill in values for local work.

This abstraction guarantees that secret access is explicit and that local and production environments load configuration identically.

## Build and Deployment Commands

Macro uses `just` (the Rust task runner) with commands defined in the root `justfile`. The commands respect the service definitions in [`services-config.json`](https://github.com/macro-inc/macro/blob/main/services-config.json).

Build a specific binary:

```bash
just build -p email_service

```

Build all Lambda handlers:

```bash
just build_lambdas

```

Deploy a service via the reusable workflow:

```yaml
- name: Deploy service
  uses: ./.github/actions/deploy-cloud-storage-pulumi
  with:
    service: email-service

```

The deployment action internally calls `get-project-name` to resolve the `stack_path`, then runs `pulumi up`.

## Complete Configuration File Reference

| File | Purpose |
|------|---------|
| [`.github/services-config.json`](https://github.com/macro-inc/macro/blob/main/.github/services-config.json) | Central service catalog; defines all source paths, binaries, Lambdas, and stack paths |
| [`.github/scripts/detect-affected-services.sh`](https://github.com/macro-inc/macro/blob/main/.github/scripts/detect-affected-services.sh) | Maps file changes to services for incremental CI builds |
| [`.github/actions/get-project-name/get-project-name.sh`](https://github.com/macro-inc/macro/blob/main/.github/actions/get-project-name/get-project-name.sh) | Resolves Pulumi stack paths from service names |
| `.env.example` | Template for local development environment variables |
| [`crates/database_env_vars/src/lib.rs`](https://github.com/macro-inc/macro/blob/main/crates/database_env_vars/src/lib.rs) | Implements `macro_env_var` for type-safe environment access |
| [`tooling/xtask/crates/xtask_workflows/src/workflows/code_check_cloud_storage.rs`](https://github.com/macro-inc/macro/blob/main/tooling/xtask/crates/xtask_workflows/src/workflows/code_check_cloud_storage.rs) | Validates Doppler configurations before builds (lines 197-209) |
| `justfile` | Defines `build`, `build_lambdas`, `test`, and other project tasks |
| `infra/stacks/*/README.md` | Per-stack documentation for Pulumi infrastructure |

## Summary

- **One file drives everything**: [`.github/services-config.json`](https://github.com/macro-inc/macro/blob/main/.github/services-config.json) is the only file you must edit to add or modify a service.
- **Declarative, not imperative**: Define what exists and where it lives; automation handles detection, building, and deployment.
- **Secrets validated at build time**: Doppler integration ensures configurations are correct before any code compiles.
- **Consistent environment handling**: The `macro_env_var` crate eliminates raw environment access and unifies local and production loading.
- **Incremental by design**: Change detection in [`detect-affected-services.sh`](https://github.com/macro-inc/macro/blob/main/detect-affected-services.sh) keeps CI fast as the monorepo grows.

## Frequently Asked Questions

### What happens if I forget to list a source path in services-config.json?

File changes in omitted directories will not trigger rebuilds for that service. The CI will skip compilation even when your code has changed, leading to stale deployments. Always include every crate, module, or generated file that the service depends on.

### Can a service have both binaries and Lambdas?

Yes. The `deploy_binaries` and `deploy_lambdas` arrays are independent. A single service entry can define multiple binaries for container deployment and multiple handlers for AWS Lambda, built and packaged separately by `just build` and `just build_lambdas`.

### How do I test my services-config.json changes locally?

Run [`./.github/scripts/detect-affected-services.sh`](https://github.com/macro-inc/macro/blob/main/./.github/scripts/detect-affected-services.sh) to verify that your globs match the intended files. Then use `just build -p <binary-name>` to confirm that the binary name in your JSON matches a valid Cargo package. No CI run is required for this validation.

### Where does the Pulumi project name come from during deployment?

The `get-project-name` action extracts it from the `stack_path` field in your service configuration. The workflow does not hardcode project names; it always resolves dynamically from [`.github/services-config.json`](https://github.com/macro-inc/macro/blob/main/.github/services-config.json), keeping deployment actions generic and reusable across all services.