Skip to content

Getting Started

This guide takes you from zero to a working sc build in about 10 minutes.

Prerequisites

  • Python 3.14+ — check with python --version
  • uv — curl -LsSf https://astral.sh/uv/install.sh | sh
  • Terraform (if you're using TerraformComponent)
  • Cloud CLI authenticated (e.g. gcloud auth application-default login for GCP)

1. Create a project

mkdir my-infra && cd my-infra
uv init
uv add small-config

2. Scaffold the project file

uv run sc init

This creates two files:

my-infra/
├── config.py          ← describe your stack here
├── .gitignore         ← sc.state.*.json, .sc/, .dist/ added automatically
└── pyproject.toml

3. Edit config.py

sc init writes a minimal stub. Here's a complete example that provisions a GCP VM with Terraform and verifies it over HTTP:

config.py
from small_config import HttpTest, Project, ProjectConfig, TerraformComponent, sc


class AppConfig(ProjectConfig):
    default_env = "dev"  # (1)

    def project(self, env: str) -> Project:
        project_id = "my-gcp-project"          # (2)
        ssh_pub_key = sc.secret("ssh_pub_key")  # (3)

        infra = TerraformComponent(
            name="infra",
            vars={
                "project_id": project_id,
                "ssh_pub_key": ssh_pub_key,
            },
        )
        return Project(
            name="my-app",
            env=env,
            components=[infra],
            tests=[
                HttpTest(
                    name="api health",
                    url=infra.output("ip", transform=lambda ip: f"http://{ip}:8000"),
                    expect_status=200,
                    expect_body_contains="Hello",
                ),
            ],
        )


CONFIG = AppConfig()
  1. default_env is used when you run sc build without --env.
  2. Hard-coded here; in a real project you'd branch on env or read from an env var.
  3. Reads SC_SECRET_SSH_PUB_KEY from the environment at deploy time.

4. Export secrets

Secrets are never stored in config.py or state files. Export them before running any command:

export SC_SECRET_SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)"

The naming rule: sc.secret("foo_bar") reads SC_SECRET_FOO_BAR. See Secrets for details.

5. Dry-run first

uv run sc plan --dry-run

This renders your Terraform templates to .dist/infra/ and prints the commands that would run — without touching any real infrastructure. Secrets are substituted with a safe placeholder in dry-run mode.

6. Deploy

uv run sc build

sc build does three things in order:

  1. Renders component templates to .dist/<component>/
  2. Applies each component (terraform init → terraform apply)
  3. Runs all HttpTest assertions and prints a result table
          Tests
┏━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Name        ┃ Result ┃
┡━━━━━━━━━━━━━╇━━━━━━━━┩
│ api health  │ PASS   │
└─────────────┴────────┘

7. Tear down

uv run sc destroy

Runs terraform destroy -auto-approve for each TerraformComponent and clears the per-env state file.

8. Use multiple environments

config.py receives the env string. Use it to branch on project IDs, instance sizes, or anything else:

def project(self, env: str) -> Project:
    project_id = "my-gcp-prod" if env == "production" else "my-gcp-dev"
    ...

Run against a specific env:

uv run sc build --env production
uv run sc destroy --env staging

Each environment keeps its own state file: sc.state.dev.json, sc.state.production.json, etc. They never interfere with each other.

What's next?