# Packer Components

Packer components build machine images (AMIs, VM images, container images)
using HashiCorp Packer. They allow you to manage image builds with the same
stack-based configuration approach used for Terraform and Helmfile.

## Available Configuration Sections

Packer components support the common configuration sections:

- **[`vars`](/stacks/vars)**
  Variables passed to packer.
- **[`env`](/stacks/env)**
  Environment variables during execution.
- **[`settings`](/stacks/settings)**
  Integrations and metadata.
- **[`metadata`](/stacks/components/component-metadata)**
  Component behavior and inheritance.
- **[`command`](/stacks/command)**
  Override packer binary.
- **[`hooks`](/stacks/hooks)**
  Lifecycle event handlers.

## Component Structure

A typical Packer component configuration:

```yaml
components:
  packer:
    ami-ubuntu:
      metadata:
        component: ami-ubuntu
      vars:
        region: us-east-1
        instance_type: t3.medium
        source_ami_filter:
          name: "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
          owner: "099720109477" # Canonical
        ami_name_prefix: "acme-ubuntu"
      env:
        AWS_PROFILE: acme-prod
```

## Packer Directory Structure

Packer components are located in the path configured in `atmos.yaml`:

```yaml
# atmos.yaml
components:
  packer:
    base_path: components/packer
```

Example structure:

```
components/packer/
├── ami-ubuntu/
│   ├── template.pkr.hcl
│   ├── variables.pkr.hcl
│   └── scripts/
│       ├── setup.sh
│       └── cleanup.sh
├── ami-eks-node/
│   ├── template.pkr.hcl
│   └── variables.pkr.hcl
└── docker-base/
    └── template.pkr.hcl
```

## Packer Template

Each Packer component contains HCL templates that use variables passed from Atmos:

**File:** `components/packer/ami-ubuntu/variables.pkr.hcl`

```hcl
variable "region" {
  type        = string
  description = "AWS region to build the AMI"
}

variable "instance_type" {
type = string
default = "t3.medium"
description = "EC2 instance type for the builder"
}

variable "source_ami_filter" {
type = object({
name = string
owner = string
})
description = "Filter for source AMI"
}

variable "ami_name_prefix" {
type = string
description = "Prefix for the AMI name"
}

variable "tags" {
type = map(string)
default = {}
description = "Tags to apply to the AMI"
}
```

**File:** `components/packer/ami-ubuntu/template.pkr.hcl`

```hcl
packer {
  required_plugins {
    amazon = {
      version = ">= 1.2.0"
      source  = "github.com/hashicorp/amazon"
    }
  }
}

source "amazon-ebs" "ubuntu" {
  region        = var.region
  instance_type = var.instance_type

  source_ami_filter {
    filters = {
      name                = var.source_ami_filter.name
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = [var.source_ami_filter.owner]
  }

  ami_name = "${var.ami_name_prefix}-ubuntu-{{timestamp}}"

  tags = merge(var.tags, {
    Name      = "${var.ami_name_prefix}-ubuntu"
    BuildTime = timestamp()
  })

  ssh_username = "ubuntu"
}

build {
  sources = ["source.amazon-ebs.ubuntu"]

  provisioner "shell" {
    scripts = [
      "${path.root}/scripts/setup.sh",
      "${path.root}/scripts/cleanup.sh"
    ]
  }
}
```

## Component-Type Defaults

Define defaults for all Packer components:

```yaml
# Apply to all Packer components
packer:
  vars:
    region: us-east-1
    tags:
      ManagedBy: Atmos
      Builder: Packer
  env:
    PACKER_LOG: "1"
    AWS_PROFILE: acme-build

# Individual components
components:
  packer:
    ami-ubuntu:
      vars:
        instance_type: t3.medium
```

## Complete Example

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

```yaml
import:
  - catalog/packer/_defaults
  - orgs/acme/plat/prod/_defaults

vars:
  region: us-east-1
  stage: prod

packer:
  vars:
    tags:
      Environment: prod
      ManagedBy: Atmos
  env:
    AWS_PROFILE: acme-prod

components:
  packer:
    ami-ubuntu-base:
      metadata:
        component: ami-ubuntu
      vars:
        instance_type: t3.medium
        ami_name_prefix: "acme-prod"
        source_ami_filter:
          name: "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
          owner: "099720109477"

    ami-eks-node:
      metadata:
        component: ami-eks-node
      vars:
        instance_type: t3.large
        ami_name_prefix: "acme-prod-eks"
        kubernetes_version: "1.28"
        source_ami_filter:
          name: "amazon-eks-node-1.28-*"
          owner: "602401143452"  # Amazon EKS
      dependencies:
        components:
          - name: ami-ubuntu-base
            kind: packer

    docker-base:
      metadata:
        component: docker-base
      vars:
        repository: "acme/base"
        tag: "{{ .stage }}-{{ timestamp }}"
        base_image: "ubuntu:22.04"
```

## Template Configuration

Atmos supports two ways to specify Packer templates:

### Directory Mode (Default)

By default, Atmos passes the component directory to Packer, which automatically loads all `*.pkr.hcl` files.
This is the recommended approach for organizing Packer configurations:

```yaml
components:
  packer:
    my-ami:
      # No settings.packer.template - uses directory mode
      # Packer loads all *.pkr.hcl files from the component directory
      vars:
        region: us-east-1
```

This allows you to organize your Packer component with multiple files:

```
components/packer/my-ami/
├── variables.pkr.hcl    # Variable declarations
├── main.pkr.hcl         # Source and build blocks
├── locals.pkr.hcl       # Local values (optional)
└── plugins.pkr.hcl      # Required plugins (optional)
```

### Single File Mode

For simple components or when you need to specify a particular template file:

```yaml
components:
  packer:
    my-ami:
      settings:
        packer:
          template: main.pkr.hcl # Use specific file
      vars:
        region: us-east-1
```

You can also use the `--template` (or `-t`) flag on the command line to override the template:

```bash
atmos packer validate my-ami -s prod --template main.pkr.hcl
```

:::tip Directory Mode Benefits
Directory mode is recommended because it:

- Follows Packer best practices for organizing configurations
- Allows separation of variables, sources, and builds into logical files
- Simplifies stack manifests by not requiring explicit template configuration
  :::

## Running Packer Commands

Atmos provides commands that wrap Packer operations:

```bash
# Initialize Packer plugins
atmos packer init ami-ubuntu -s plat-ue1-prod

# Validate templates
atmos packer validate ami-ubuntu -s plat-ue1-prod

# Build images
atmos packer build ami-ubuntu -s plat-ue1-prod

# Run any packer subcommand
atmos packer <subcommand> <component> -s <stack>
```

## Environment Variables

Common environment variables for Packer components:

- **`PACKER_LOG`**
  Enable logging (set to 
  `1`
  ).
- **`PACKER_LOG_PATH`**
  Path to log file.
- **`AWS_PROFILE`**
  AWS profile for authentication.
- **`AWS_REGION`**
  Default AWS region.
- **`PACKER_CACHE_DIR`**
  Directory for Packer cache.

## Variables File Generation

Atmos generates a variables file that Packer uses during builds:

```json
// atmos-packer.pkrvars.json (auto-generated)
{
  "region": "us-east-1",
  "instance_type": "t3.medium",
  "ami_name_prefix": "acme-prod",
  "source_ami_filter": {
    "name": "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*",
    "owner": "099720109477"
  },
  "tags": {
    "Environment": "prod",
    "ManagedBy": "Atmos"
  }
}
```

## Passing Secrets Securely

The generated variables file is written to **disk in plaintext** (world‑readable, `0644`) so Packer
can read it. Unlike Terraform — which keeps secret‑bearing `vars` off disk — Packer performs **no**
off‑disk partitioning. **Never put secrets in a Packer component's `vars` section.**

:::warning Secrets belong in `env`, not `vars`
Values in the `env` section are injected only into the Packer subprocess environment and are
**never written to the variables file**. Declare secrets under
[`secrets.vars`](/cli/configuration/secrets#declaring-secrets), reference them with
[`!secret`](/functions/yaml/secret) in `env`, and read them in the Packer template with the
[`env` function](https://developer.hashicorp.com/packer/docs/templates/hcl_templates/functions/contextual/env).
:::

**File:** `stacks/deploy/prod.yaml`

```yaml
components:
  packer:
    bastion:
      secrets:
        vars:
          GITHUB_TOKEN:
            description: "Token for provisioners that clone private repos"
            store: ssm-secrets        # a `secret: true` store; or use `sops: <provider>`
            required: true
      env:
        # Injected ONLY into the Packer subprocess environment — never written to the
        # variables file on disk.
        GITHUB_TOKEN: !secret GITHUB_TOKEN
      vars:
        region: us-east-1             # non-secret values only
```

Read the secret from the environment in the Packer template (Packer's `env` function is allowed in
variable defaults and `locals`):

**File:** `components/packer/bastion/build.pkr.hcl`

```hcl
variable "github_token" {
  type      = string
  sensitive = true
  default   = env("GITHUB_TOKEN")   # read from the environment, not the variables file
}
```

## Multi-Cloud Support

Packer components can build images for multiple cloud providers:

### AWS AMI

```yaml
components:
  packer:
    ami-builder:
      vars:
        region: us-east-1
        source_ami_filter:
          name: "ubuntu/images/*"
          owner: "099720109477"
```

### Azure VM Image

```yaml
components:
  packer:
    azure-image:
      vars:
        azure_subscription_id: '{{ env "ARM_SUBSCRIPTION_ID" }}'
        azure_resource_group: "packer-images-rg"
        azure_location: "eastus"
      env:
        ARM_CLIENT_ID: '{{ env "ARM_CLIENT_ID" }}'
        ARM_CLIENT_SECRET: '{{ env "ARM_CLIENT_SECRET" }}'
```

### GCP Machine Image

```yaml
components:
  packer:
    gcp-image:
      vars:
        project_id: "my-gcp-project"
        zone: "us-central1-a"
        source_image_family: "ubuntu-2204-lts"
      env:
        GOOGLE_APPLICATION_CREDENTIALS: "/path/to/credentials.json"
```

### Docker Image

```yaml
components:
  packer:
    docker-image:
      vars:
        repository: "myregistry/myimage"
        tag: "latest"
        base_image: "ubuntu:22.04"
      env:
        DOCKER_HOST: "unix:///var/run/docker.sock"
```

## Best Practices

1. **Version Pin Plugins:** Always specify explicit plugin versions in your Packer templates.

2. **Use Source AMI Filters:** Use filters with `most_recent = true` to automatically get the latest base images.

3. **Tag Everything:** Apply consistent tags to all built images for tracking and cost allocation.

4. **Use Provisioner Scripts:** Keep provisioning logic in external scripts for easier testing and maintenance.

5. **Clean Up:** Include cleanup scripts to reduce image size and remove sensitive data.

6. **Use Dependencies:** Define `dependencies.components` when images need to be built in a specific order.

7. **Centralize Defaults:** Define common settings in catalog defaults and override only when necessary.

## Related

- [Component Types Overview](/stacks/components)
- [Terraform Components](/stacks/components/terraform)
- [Configure Variables](/stacks/vars)
- [Configure Environment](/stacks/env)
- [Passing Secrets](/cli/configuration/secrets)
- [`!secret` function](/functions/yaml/secret)
- [Component Dependencies](/stacks/dependencies/components)
