# Source-Based Version Pinning

_Atmos Design Pattern_

**Source-Based Version Pinning** enables native per-environment version control directly in stack configuration using the top-level `source` field. Components declare their source location inline, and Atmos vendors them just-in-time during execution—no separate vendor manifests or pre-vendoring required.

This approach provides strict version pinning with minimal operational overhead. Each environment can pin to specific versions while inheriting common source configuration through Atmos's stack inheritance system.

> **Key points**
>
> - Native version pinning per environment without vendor.yaml files
> - Just-in-time vendoring during terraform/helmfile execution
> - Version inheritance through Atmos stack inheritance
> - Simpler alternative to traditional vendoring for most use cases
> - Supports all go-getter protocols (git, s3, http, oci, etc.)

## Use Cases

Use **Source-Based Version Pinning** when:

- You need **per-environment version control** without vendor file overhead
- You want **versions defined alongside** other component configuration
- You need **just-in-time vendoring** without pre-vendoring steps
- You want to leverage **stack inheritance** for version defaults
- You're **prototyping or iterating quickly** on component versions

## Problem

Traditional strict version pinning requires:

1. Maintaining `vendor.yaml` with source definitions for each version
2. Running `atmos vendor pull` before deployments
3. Committing vendored code to the repository
4. Coordinating version updates across vendor manifests and stack configs
5. Managing disk space for multiple vendored versions

This creates operational overhead, especially when different environments need different versions of many components.

## Solution

Use the `source` field directly in component configuration. The source provisioner handles just-in-time vendoring automatically. Source provisioning is currently experimental.

For independent versions in different stacks and automatic refresh on version, URI, or TTL changes, enable [workdir provisioning](/stacks/components/provision/workdir). For example, import this stack default alongside the component configurations below:

```yaml
terraform:
  provision:
    workdir:
      enabled: true
```

Without an isolated workdir, Atmos downloads to the component directory on first use and normally reuses it while it is non-empty. If Atmos owns the downloaded remote source (recorded in `.atmos/source.json`) and a TTL is configured, Atmos can refresh that directory automatically when the TTL expires. Use `atmos terraform source pull <component> -s <stack> --force` to refresh that directory explicitly.

### String Form (Simple)

For simple cases, specify a go-getter-compatible URI:

**File:** `stacks/prod/us-east-1.yaml`

```yaml
components:
  terraform:
    vpc:
      source: "github.com/cloudposse/terraform-aws-components//modules/vpc?ref=1.450.0"
      vars:
        cidr_block: "10.0.0.0/16"
```

### Map Form (Full Control)

For more control, use a map with explicit fields:

**File:** `stacks/prod/us-east-1.yaml`

```yaml
components:
  terraform:
    vpc:
      source:
        uri: github.com/cloudposse/terraform-aws-components//modules/vpc
        version: 1.450.0
        included_paths:
          - "*.tf"
          - "modules/**"
        excluded_paths:
          - "*.md"
          - "tests/**"
      vars:
        cidr_block: "10.0.0.0/16"
```

### Map Form with Retry Configuration

For unreliable networks or rate-limited sources, configure retry behavior:

**File:** `stacks/prod/us-east-1.yaml`

```yaml
components:
  terraform:
    vpc:
      source:
        uri: github.com/cloudposse/terraform-aws-components//modules/vpc
        version: 1.450.0
        retry:
          max_attempts: 5
          initial_delay: 2s
          max_delay: 60s
          backoff_strategy: exponential
          random_jitter: 0.1
      vars:
        cidr_block: "10.0.0.0/16"
```

**Retry options:**

| Field | Default | Description |
|-------|---------|-------------|
| `max_attempts` | 1 | Maximum number of download attempts |
| `initial_delay` | 100ms | Initial delay before first retry |
| `max_delay` | 5s | Maximum delay between retries |
| `backoff_strategy` | exponential | Strategy: `constant`, `linear`, or `exponential` |
| `multiplier` | 2.0 | Backoff multiplier for exponential/linear strategies |
| `random_jitter` | 0.0 | Randomness added to delays (0.1 = 10%) |
| `max_elapsed_time` | 30m | Maximum total time for all retries |

### Cache TTL for Floating Refs

When using floating refs like branch names, the version string doesn't change even when upstream content does. Use `ttl` to control how long cached sources are reused:

**File:** `stacks/dev/us-east-1.yaml`

```yaml
components:
  terraform:
    my-module:
      source:
        uri: git::https://github.com/org/repo.git
        version: develop
        ttl: "0s"    # Always re-pull (active development)
      vars:
        enabled: true
```

A global default TTL can be set in `atmos.yaml`:

**File:** `atmos.yaml`

```yaml
components:
  terraform:
    source:
      ttl: "1h"     # Re-pull sources older than 1 hour
```

| TTL Value | Use Case |
|-----------|----------|
| Not set | Stable releases — cache indefinitely, only re-pull on version/URI change |
| `"0s"` | Active development — always get the latest from upstream |
| `"1h"` | Team collaboration — hourly refresh catches colleagues' pushes |
| `"7d"` | Slow-moving dependencies — weekly check for updates |

### Version Inheritance

Define source defaults in a catalog and override versions per environment:

### Catalog Defaults

**File:** `stacks/catalog/vpc/defaults.yaml`

```yaml
components:
  terraform:
    vpc/defaults:
      metadata:
        type: abstract
      source:
        uri: github.com/cloudposse/terraform-aws-components//modules/vpc
        version: 1.450.0  # Default version
        included_paths:
          - "*.tf"
          - "modules/**"
```

### Development

**File:** `stacks/dev/us-east-1.yaml`

```yaml
import:
  - catalog/vpc/defaults

components:
  terraform:
    vpc:
      metadata:
        inherits: [vpc/defaults]
      source:
        version: 1.451.0  # Latest version in dev
      vars:
        cidr_block: "10.0.0.0/16"
```

### Production

**File:** `stacks/prod/us-east-1.yaml`

```yaml
import:
  - catalog/vpc/defaults

components:
  terraform:
    vpc:
      metadata:
        inherits: [vpc/defaults]
      source:
        version: 1.450.0  # Stable version in prod
      vars:
        cidr_block: "10.2.0.0/16"
```

## How It Works

Sources are automatically provisioned when running terraform commands:

```shell
# Just run terraform - source is provisioned automatically
atmos terraform plan vpc --stack dev
# → Auto-provisioning source for component 'vpc'
# → Auto-provisioned source to components/terraform/vpc
# → Terraform runs
```

Atmos resolves the source and provisions it before Terraform initialization. With workdir provisioning enabled, each component instance runs in an isolated directory, and source metadata controls refreshes when the URI or version changes or the configured TTL expires. Without workdir provisioning, Atmos normally reuses a non-empty component directory, but an Atmos-owned remote source can refresh automatically when its configured TTL expires. Use `atmos terraform source pull <component> -s <stack> --force` for an explicit refresh.

Path filters control which source files are copied. Terraform runs against the resolved local working directory in either mode.

## CLI Commands

Explicit commands for managing sources:

```bash
# Vendor component source (downloads if missing or outdated)
atmos terraform source pull vpc --stack prod-us-east-1

# Force re-vendor even if up-to-date
atmos terraform source pull vpc --stack prod-us-east-1 --force

# List components with source configured
atmos terraform source list --stack prod-us-east-1

# Show source configuration
atmos terraform source describe vpc --stack prod-us-east-1

# Remove vendored source
atmos terraform source delete vpc --stack prod-us-east-1 --force
```

See [atmos terraform source](/cli/commands/terraform/source) for complete CLI documentation.

## Rollback Strategy

Rolling back is simple—change the version in your stack configuration:

```yaml
# Before: problematic version
source:
  version: 1.451.0

# After: rollback to stable version
source:
  version: 1.450.0
```

Then apply:

```bash
# Force re-vendor to get previous version
atmos terraform source pull vpc --stack prod-us-east-1 --force

# Apply rollback
atmos terraform apply vpc --stack prod-us-east-1
```

## Benefits

- **No vendor files to maintain**: Version control happens in stack config
- **No components committed to repo**: Components don't bloat your repository if that's not adding value for your workflow
- **Smaller repositories**: No vendored Terraform code means smaller git repos and faster clones
- **Per-environment versions**: Each environment pins its own version natively
- **Stack inheritance**: Define defaults once, override where needed
- **Just-in-time**: No pre-vendor step in CI/CD pipelines
- **Simple configuration**: Fewer files, less coordination overhead

## Drawbacks

- **No local immutable copy**: Components aren't committed, so you lose the audit trail in Git
- **No pre-vendored code in repo**: Can't review component diffs before deploy
- **AI coding assistants lack context**: Tools like Claude Code, Cursor, and GitHub Copilot work better with vendored code in the repo—they can't see components that haven't been downloaded yet
- **Requires network access**: Components downloaded during execution
- **No local modifications**: Can't patch vendored components
- **Less suitable for air-gapped**: Needs external network access during deployment
- **Harder to search codebase**: Can't grep component code without first downloading it

## When to Use Source vs Vendoring

| Requirement | Source | Vendoring |
|------------|--------|-----------|
| Per-environment versions | ✓ | ✓ |
| No vendor manifest files | ✓ | ✗ |
| Code review of dependencies | ✗ | ✓ |
| Offline deployment | ✗ | ✓ |
| Local modifications | ✗ | ✓ |
| Audit trail in Git | ✗ | ✓ |
| AI coding assistant context | ✗ | ✓ |
| Minimal operational overhead | ✓ | ✗ |

## Best Practices

1. **Use catalog defaults**: Define base source config in catalogs
2. **Override only version**: Inherit everything except version per environment
3. **Use map form for filtering**: When you need to exclude files
4. **Consider vendoring for production-critical**: When audit trails matter
5. **Pre-vendor in CI**: Run `atmos terraform source pull` in CI for faster deploys

## Related Patterns

- [Strict Version Pinning](./strict-version-pinning) - Traditional approach using vendoring
- [Vendoring Components](./vendoring-components) - Pre-vendor for audit trails and offline use
- [Folder-Based Versioning](./folder-based-versioning) - Organize vendored versions in folders
- [Release Tracks/Channels](./release-tracks-channels) - Moving version targets vs fixed pins
