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

Macro uses a single JSON file—.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. 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:

{
  "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 for change detection, 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. No manual registration steps are required.

Change Detection

The script .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:

./.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 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 and add an entry following the established pattern:

{
  "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 (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.

Usage in application code:

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.

Build a specific binary:

just build -p email_service

Build all Lambda handlers:

just build_lambdas

Deploy a service via the reusable workflow:

- 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 Central service catalog; defines all source paths, binaries, Lambdas, and stack paths
.github/scripts/detect-affected-services.sh Maps file changes to services for incremental CI builds
.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 Implements macro_env_var for type-safe environment access
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 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 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 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, keeping deployment actions generic and reusable across all services.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →