# 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.

### 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
```

**Under the hood:**

1. **Source Provisioner Hook**: Registers for `before.terraform.init` event
2. **Configuration Check**: Extracts `source` from component configuration
3. **Skip if Exists**: If target directory exists and is non-empty, skip provisioning
4. **Download**: Uses go-getter to fetch component at specified version
5. **Path Filtering**: Applies `included_paths` and `excluded_paths` patterns
6. **Target Directory**: Places component in the configured component path
7. **Execution**: Terraform runs against the vendored component

The source provisioner only downloads if the component directory doesn't exist or is empty.

## 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
