Migrating from Fogg
Adopt Atmos around your existing Fogg-generated Terraform, then maintain your infrastructure with Atmos stacks, Terraform components, and workflows. Keep your state and resource addresses intact while replacing Fogg's configuration generation and Make targets incrementally.
This guide assesses equivalent behavior. Explicit Terraform code, Atmos configuration, and templates count as parity; continuing to run Fogg does not. An automatic importer is not required.
The comparison covers Fogg configuration version 2 and source revision
6b897917. The published configuration
reference does not describe every capability in that
revision. Inspect your generated files and pinned Fogg version before applying the examples, especially for custom templates,
plugins, and TFE integration.
Fogg's fogg apply generates files. Its generated make apply runs Terraform.
atmos terraform apply changes infrastructure. Use atmos describe component to inspect configuration and atmos terraform generate files when you explicitly want to generate auxiliary files.
Feature Parity
Supported means an existing Atmos capability provides the behavior. Supported with explicit configuration means you supply the Terraform declarations or configuration shown below. Gap means the assessed behavior needs additional implementation; a generic ability to run a script is not evidence of parity.
| Fogg capability | Status | Atmos implementation |
|---|---|---|
| Global, account, and environment root modules | Supported | Components pointing at existing directories; stacks for deployment configuration |
| Defaults and per-component overrides | Supported | Stack imports, component inheritance, and vars |
| S3 or remote backend and state locking | Supported with explicit configuration | Preserve the existing backend, or reproduce it with backend_type and backend |
| Provider configuration and regional/account aliases | Supported with explicit configuration | Terraform provider blocks or providers, preserving aliases, roles, account restrictions, and tags |
| Module invocation, provider mappings, output forwarding | Supported with explicit configuration | Maintain ordinary Terraform wrappers, create them from scaffolds, or regenerate them with explicit generate templates |
| Global/account/sibling remote-state access | Supported with explicit configuration | Terraform data sources or !terraform.output; declare each required relationship |
| Component ordering | Supported | dependencies.components and graph-backed Terraform commands or ordered workflows |
| Terraform versions and CLI installation | Supported | dependencies.tools and toolchain registries |
| Component templates and extra generated files | Supported with explicit configuration | Imports/inheritance for configuration, scaffolds for initial files, generate for runtime files |
| Formatting, linting, validation, authentication, plan/apply | Supported with explicit configuration | Terraform commands, hooks, and the custom command below |
| CircleCI, Travis CI, and GitHub Actions checks | Supported with explicit configuration | Run Atmos custom commands in the existing CI runner; preserve credentials, triggers, and approvals |
| EKS authentication and Kubernetes provider wiring | Supported with explicit configuration | Atmos Auth EKS integrations and tokens, plus Terraform provider configuration |
| Custom CLI downloads and custom Terraform providers | Supported with explicit configuration | Toolchain registries for CLIs; Terraform provider installation for providers |
| TFE workspace-management Terraform | Supported with explicit configuration | Manage the existing TFE root module as a component and maintain its inputs/catalog explicitly |
| Fogg's ten-minute cached refresh policy | Gap | No Fogg-equivalent refresh cache is configured by Atmos; use normal plan refresh, or implement the cache policy separately |
There is no identified blocker to managing the existing infrastructure with Atmos. The refresh cache gap affects command behavior and performance, not the ability to plan and apply Terraform. See operational differences before changing CI expectations.
Adopt Existing Terraform First
Inventory the Current Contract
For each deployable root module, record:
- Directory, Terraform executable/version, provider source addresses/versions, and lockfile if present.
- Backend type and complete configuration, including state key, workspace, locking, and credentials.
- Module labels, provider aliases and module mappings, output names and sensitivity, variable defaults, locals, and environment variables supplied by Make or CI.
- Remote-state consumers, dependency ordering, custom files, plugin paths, and CI checks.
Fogg's term workspace commonly means a root-module directory. It does not tell you the selected Terraform CLI workspace. Check
terraform workspace show using the existing pinned executable in each initialized directory. Record TF_WORKSPACE if your
automation sets it.
Capture a baseline plan using your established workflow and retain a state backup through your normal backend backup process. Freeze Terraform/provider versions during adoption. Do not combine this change with backend moves, resource renaming, or a Terraform upgrade.
Keep the Directory Layout
This example assumes these root modules already exist:
terraform/
├── global/
├── accounts/main-account/
├── envs/development/
│ ├── vpc/
│ └── webserver/
└── modules/webserver/ # Child module, not a separate deployment
Keep fogg.tf, generated main.tf/outputs.tf, hand-written files, and relative module source paths. Install
Atmos, then create:
base_path: .
components:
terraform:
base_path: terraform
auto_generate_backend_file: false
auto_generate_files: false
apply_auto_approve: false
stacks:
base_path: stacks
included_paths:
- deploy/**/*
Backend generation is disabled because fogg.tf already declares the backend. Leave providers and generate unset during
this first step so the original Terraform remains authoritative.
name: development
components:
terraform:
vpc:
metadata:
component: envs/development/vpc
terraform_workspace: default
webserver:
metadata:
component: envs/development/webserver
terraform_workspace: default
name: shared
components:
terraform:
global:
metadata:
component: global
terraform_workspace: default
main-account:
metadata:
component: accounts/main-account
terraform_workspace: default
These examples assume the recorded CLI workspace is default; substitute the actual workspace for each component. Each root
retains its own state location. Deploy shared roots only through shared, rather than creating a second deployment of the same
state in every environment stack.
atmos validate stacks
atmos describe component webserver -s development
atmos describe component global -s shared
atmos terraform plan webserver -s development
Before planning, make the same Terraform executable and credentials available that Make used. The tool selection example below makes this reproducible. Compare the resolved component path and workspace, then compare the Atmos plan with the baseline. Stop if Atmos targets empty state, proposes unexpected replacements, or changes provider/account selection. See brownfield considerations.
Maintain the Configuration Without Fogg
Stop running fogg apply for migrated files and remove it from their CI jobs. The generated Terraform is now maintained source
code: replace obsolete generated-file notices when taking ownership. You can continue editing those files directly without
replacing every declaration with YAML.
During a mixed migration, Fogg may still regenerate files throughout the repository. Keep its configuration for migrated roots frozen and review generated diffs, or stop generation for the whole repository before taking ownership. Assign one apply pipeline per state.
Defaults, Inputs, and New Environments
Map defaults to imported defaults, envs to stacks, and component overrides to component configuration. Fogg owner,
project, and extra_vars can become Terraform inputs under vars. Keep their existing variable declarations and types;
setting a stack value does not declare a Terraform variable. Fogg locals also remain locals until you explicitly refactor them.
For roots that already declare owner and project, add:
terraform:
vars:
owner: infra@example.com
project: acme
Import it at the top of each applicable deployment manifest:
import:
- catalog/defaults
Use the exact existing values during migration. Introduce component inheritance for reusable configurations as needed. Create future roots from maintained Terraform or scaffolds, then add stack entries pointing at them. A new environment needs its own state identity. Consolidating existing roots into one reusable directory is optional and should follow plan equivalence, especially when their locals differ.
Backend Configuration and State Identity
Keeping the backend in Terraform is supported permanently. If you move it into stack backend configuration,
remove only the original backend block and enable components.terraform.auto_generate_backend_file after every root affected by
that setting is ready. Do not remove the rest of fogg.tf.
For an existing S3 backend, copy its actual values. This illustrative component fragment assumes the existing key is
terraform/acme/envs/development/components/webserver.tfstate:
components:
terraform:
webserver:
metadata:
component: envs/development/webserver
terraform_workspace: default
backend_type: s3
backend:
s3:
bucket: acme-terraform-state
key: terraform/acme/envs/development/components/webserver.tfstate
region: us-west-2
profile: acme-state
encrypt: true
dynamodb_table: acme-terraform-locks
workspace_key_prefix: "env:"
The key above matches this component's convention in the inspected Fogg
source, but your actual
backend is authoritative. Preserve the existing locking mechanism and role configuration using syntax accepted by your pinned
Terraform version. Provider credentials and backend credentials are independent. Do not infer one from the other. For S3, the
default workspace uses key directly; named workspaces additionally use workspace_key_prefix and the workspace name. If
previously omitted, Terraform's S3 prefix is env:. See Terraform's S3 backend
reference. Do not replace it with an Atmos naming convention
during adoption.
DynamoDB locking is deprecated. After validating the Atmos migration, recommend migrating to
S3 lockfiles with backend.s3.use_lockfile: true on Terraform 1.10 or
later. Give the backend identity
s3:GetObject, s3:PutObject, and s3:DeleteObject on the .tflock object, including workspace
paths. During a staged rollout, keep dynamodb_table alongside use_lockfile so upgraded
clients acquire both locks. Remove DynamoDB locking only after every writer uses S3 locking;
DynamoDB-only and S3-only writers do not coordinate. Keep the state bucket/key unchanged and
handle any Terraform upgrade separately. See the S3 locking
reference.
For a Terraform Cloud/Enterprise remote backend, retain the hostname, organization, and exact remote workspace name. The
remote workspace name is distinct from the local CLI workspace:
backend_type: remote
backend:
remote:
hostname: app.terraform.io
organization: acme
workspaces:
name: acme-development-webserver
Place this fragment on the component. Keep the remote workspace's execution mode, variables, credentials, VCS settings, and
Terraform version. If the existing remote execution setup rejects saved planfiles, use atmos terraform plan webserver -s development --skip-planfile. Adoption does not require terraform init -migrate-state, state mv, or importing resources.
Providers, Aliases, and Tags
Fogg's provider configuration combines several concerns. Preserve each separately:
- Provider source addresses and version constraints remain in
terraform.required_providers. - Provider arguments can remain in HCL or move to Atmos
providers. - Regional/account aliases must keep their names, including references in child-module
providersmaps. - AWS
allowed_account_ids, assume-role configuration, default tags, and anyTFC_*metadata values must retain their effective values to avoid changing resources during migration.
For example, this fragment configures a primary AWS provider and an existing us-east-1 alias:
providers:
aws:
region: us-west-2
profile: acme-deploy
allowed_account_ids: ["123456789012"]
assume_role:
role_arn: arn:aws:iam::123456789012:role/terraform-role
aws.us-east-1:
region: us-east-1
profile: acme-deploy
allowed_account_ids: ["123456789012"]
assume_role:
role_arn: arn:aws:iam::123456789012:role/terraform-role
Atmos generates providers_override.tf.json. This example intentionally leaves existing default_tags in HCL; inspect the
effective override before moving those too. Do not replace Fogg's tags with Atmos naming/tag conventions as part of the tool
migration. An alias does not inherit the primary provider's arguments automatically, so configure each alias explicitly.
Module Calls and Outputs
Fogg's module_source, module_name, and provider_aliases produce ordinary Terraform. Maintain the generated wrapper as the
Atmos component. For a wrapper whose existing label is webserver, its shape remains:
module "webserver" {
source = "../../../modules/webserver"
# Preserve the existing arguments and expressions.
vpc_id = local.vpc_id
providers = {
aws = aws.us-east-1
}
}
output "endpoint" {
value = module.webserver.endpoint
}
output "credentials" {
value = module.webserver.credentials
sensitive = true
}
This illustrates an existing child module that declares vpc_id and those outputs; use your module's actual interface. Keep its
current label rather than renaming it to match this example. Renaming a module changes resource addresses. Replacing a wrapper
with its child module as the root also changes addresses, so Atmos source provisioning is not a drop-in replacement for the
wrapper.
When changing the child module later, update arguments and forwarded outputs in this maintained Terraform code, or update your scaffold/template. Module introspection is not required to deploy the same resources or expose the same outputs. For roots containing several child modules, see common questions for a reusable generation example.
Remote State and Execution Ordering
Fogg generates remote-state data sources for global, account, and sibling component state. Keep them unchanged initially. They continue to work without Fogg because Terraform performs the reads. Maintain their backend locations and output names when adding new consumers.
Alternatively, declare Terraform inputs and supply outputs through Atmos. For a webserver with declared vpc_id and zone_id
inputs, add this component fragment:
dependencies:
components:
- name: vpc
- name: global
stack: shared
vars:
vpc_id: !terraform.output vpc vpc_id
zone_id: !terraform.output global shared zone_id
Update Terraform references to use those inputs before removing the old data sources. !terraform.output reads an output; it
does not apply the producer. dependencies.components declares ordering; it does not pass values. Do not translate every
automatically generated remote-state data source into a dependency edge: declare actual producer/consumer relationships and
avoid cycles.
# Include the producer components and plan in dependency order.
atmos terraform plan --components webserver -s development --include-dependencies
See component dependencies and !terraform.output. An
output must exist before it can be read. Planning a new producer does not create its outputs; apply producers first during
initial bootstrap, then plan consumers. Keep Terraform-side data sources when remote execution must read outputs itself rather
than accepting values resolved on the Atmos runner.
To publish selected outputs outside Terraform state, see the stores example in common questions.
Terraform Versions and Tools
Fogg's generated Makefiles select Terraform through tfenv. Atmos can select the same version with tool dependencies:
terraform:
dependencies:
tools:
terraform: "1.9.8"
Place this in imported defaults, replacing 1.9.8 with your existing version. Keep the matching required_version constraint
in Terraform. A component's dependencies.tools can override the default for roots on other versions. This is version
selection, not an upgrade instruction. Check that binaries for your existing version are available on the target platform.
Templates and Generated Files
Translate configuration reuse from component_templates into imports/inheritance. For files:
- Use scaffolds for initial root modules and files developers will maintain.
- Use
generatefor files that should be regenerated from stack configuration. This feature is currently experimental; retaining maintained HCL does not require it. - Keep root-level CI files and
.gitignoreunder version control, or manage them through an explicit scaffold.generatewrites into the resolved component directory.
For example, after removing the corresponding declaration from the original generated file:
generate:
migration.generated.tf:
locals:
project_owner: "{{ .vars.owner }}"
atmos terraform generate files webserver -s development --dry-run
atmos terraform generate files webserver -s development
Set components.terraform.auto_generate_files: true when generation should happen during Terraform commands. Add the generated
filename to .gitignore. Keep each declaration owned by one file; do not let Fogg, Atmos, and a developer all rewrite the same
file. Preserve an extra_templates file intended to be created only once as maintained source or a scaffold, rather than an
always-regenerated file.
See common questions for generating module calls and retaining TypeScript and monorepo tooling.
Replace Make and CI
| Existing operation | Atmos operation |
|---|---|
fogg init / repository layout setup | Create atmos.yaml and stacks; use a scaffold for future repositories |
fogg apply | Edit maintained Terraform/stacks; run file generation only for configured templates |
make setup | Install .tool-versions entries with atmos toolchain install, or specify tool@version; configure credentials separately |
make plan | atmos terraform plan webserver -s development |
make apply | atmos terraform apply webserver -s development |
make run CMD='output' | atmos terraform output webserver -s development |
make check / CI checks | The atmos check-webserver custom command below |
This custom command assumes the pinned Terraform, AWS CLI, and TFLint are available through your toolchain or runner. The two AWS profiles must be the same profiles used by the backend and provider. If they assume roles, configure those profiles to assume the intended roles so the identity checks exercise the same credentials.
Add this commands entry to atmos.yaml:
commands:
- name: check-webserver
description: Check the development webserver without applying changes
steps:
- name: backend-identity
type: shell
command: aws --profile acme-state sts get-caller-identity
- name: provider-identity
type: shell
command: aws --profile acme-deploy sts get-caller-identity
- name: format
type: atmos
command: terraform fmt webserver -s development -check -diff
- name: initialize
type: atmos
command: terraform init webserver -s development
- name: lint
type: shell
working_directory: terraform/envs/development/webserver
command: tflint
- name: validate
type: atmos
command: terraform validate webserver -s development
- name: plan
type: shell
command: |
status=0
atmos terraform plan webserver -s development -detailed-exitcode || status=$?
case "$status" in
0|2) exit 0 ;;
*) exit "$status" ;;
esac
Run it with atmos check-webserver; inspect it with atmos check-webserver --help. For provider-specific TFLint rulesets, preserve .tflint.hcl and install the
plugins with tflint --init as part of runner setup. Keep formatting checks separate from formatting that rewrites files. For
checks on every Terraform invocation, lifecycle hooks can run TFLint instead of the explicit custom-command step.
In CircleCI, Travis CI, or GitHub Actions, replace the Make invocation with this custom command after installing the pinned Atmos
version and providing the existing credentials. Preserve triggers, required-check names, SSH access to private modules,
environment variables, and apply approvals. For Heroku or other providers, preserve their credential mechanism and replace
AWS-specific identity checks with the appropriate checks. For deployment, review the plan and use atmos terraform apply webserver -s development --from-plan where saved plans are supported. See Makefile migration for broader
task-runner mappings.
Operational Differences
Fogg's component Make targets refresh state separately and cache that refresh for ten minutes for local execution. Atmos's normal plan uses Terraform's refresh behavior. The custom command above intentionally uses a fresh plan rather than reproducing this cache.
Remaining gap: there is no ready-made Fogg refresh-cache policy here. If identical caching is required, additional
implementation must track the last successful refresh per backend/workspace, refresh after expiry, skip the cache for remote
execution, and invalidate it when state identity changes. Test expiry, failed refreshes, concurrent runs, and workspace
isolation before relying on it. Simply passing -refresh=false does not reproduce the behavior.
Atmos hooks are the integration point for a custom refresh-cache policy:
run it on before.terraform.plan with on_failure: fail so a refresh failure stops planning.
The policy must initialize/select the same backend and workspace, refresh successfully before
recording a timestamp, and coordinate concurrent readers/writers. Only after that succeeds may
the outer plan use -refresh=false. Do not recursively invoke the same plan hook. A hook's
when: local distinguishes the runner environment; it does not detect a Terraform remote
backend. Hooks support this orchestration, but do not themselves supply the ten-minute cache.
The gap is the cache-policy implementation, not hook support.
Fogg's check-plan treats Terraform exit code 2 (changes present) as success; the custom command above does the same while preserving
failures. It retains Terraform's state locking instead of copying Fogg's -lock=false. Normal Atmos plans save a planfile, so
treat that artifact as potentially sensitive and follow your CI retention/access policy. Remote execution may require
--skip-planfile, as described above. Do not assume the two tools have identical refresh, artifact, or approval defaults.
Advanced Integrations
EKS and Kubernetes Providers
Fogg's EKS support ultimately produces Terraform provider wiring. Keep existing aws_eks_cluster and aws_eks_cluster_auth
data sources and Kubernetes/Helm/kubectl provider blocks in the component, preserving their names and provider versions.
Terraform will still resolve the endpoint, CA certificate, and credentials without Fogg.
For new configuration, use Atmos Auth to manage the AWS identity
and refresh EKS credentials through atmos aws eks token. The following provider example
assumes an Atmos identity named dev-admin with access to the cluster in us-west-2:
variable "cluster_name" {
type = string
}
data "aws_eks_cluster" "cluster" {
name = var.cluster_name
}
provider "kubernetes" {
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "atmos"
args = [
"aws", "eks", "token",
"--cluster-name", var.cluster_name,
"--region", "us-west-2",
"--identity", "dev-admin"
]
}
}
Declare the required AWS and Kubernetes providers and pass cluster_name through stack vars.
Match the identity, region, and AWS provider credentials to the existing deployment, for example
with atmos terraform plan webserver -s development --identity dev-admin. Keep Helm/kubectl
configuration appropriate to the pinned provider versions. Terraform's execution environment
must have Atmos and its auth configuration available for the exec command.
For kubectl and other kubeconfig consumers, add an aws/eks integration referencing that
existing Atmos identity:
auth:
integrations:
development-eks:
kind: aws/eks
via:
identity: dev-admin
spec:
cluster:
name: acme-development
region: us-west-2
alias: acme-development
atmos auth login --identity dev-admin
atmos aws eks update-kubeconfig --integration development-eks
The generated kubeconfig uses Atmos to obtain fresh EKS tokens. Configure the identity and cluster access first; authenticating to AWS does not itself grant Kubernetes permissions. Terraform provider configuration and a shell's kubectl context remain separate. See the EKS authentication tutorial for identity setup, kubeconfig paths, and integration options. These account-dependent examples require validation against your cluster.
Custom Binaries and Providers
Fogg's plugin installer downloads binaries or extracts archives. For a custom CLI, declare an Atmos toolchain registry, for example:
toolchain:
registries:
- name: internal-tools
type: atmos
priority: 150
tools:
acme/infra-check:
type: http
url: "https://artifacts.example.com/infra-check/{{.Version}}/{{.OS}}/{{.Arch}}/infra-check"
Replace the illustrative URL with your binary distribution, then declare dependencies.tools: { acme/infra-check: "1.2.3" } on
the command or workflow that runs it. For archives, configure the matching format and file mapping in the registry. Update
scripts that hard-code .fogg/bin to invoke the tool on PATH.
Terraform provider plugins are different from CLI tools. Preserve their source addresses and versions in required_providers.
Use a provider registry or Terraform's provider_installation configuration with a filesystem/network mirror. For an existing
unpacked mirror:
provider_installation {
filesystem_mirror {
path = "/opt/terraform-provider-mirror"
include = ["registry.example.com/acme/*"]
}
direct {
exclude = ["registry.example.com/acme/*"]
}
}
Provision the mirror on each runner in Terraform's required host/namespace/type/version/platform layout and set
TF_CLI_CONFIG_FILE to the absolute path of this file. See Terraform's provider installation
reference. Atmos selecting a CLI binary
does not by itself install that binary as a Terraform provider. If you only have legacy loose binaries, packaging the mirror
remains required migration work.
Terraform Enterprise Management
Fogg's TFE integration may also manage Terraform that creates remote workspaces, team access, and VCS/check settings. Register
that existing root module, typically terraform/tfe, as another component in the shared stack:
components:
terraform:
tfe:
metadata:
component: tfe
terraform_workspace: default
Use its recorded workspace, retain its TFE provider and backend, and take ownership of its generated locals.tf and
locals.tf.json. Maintain the accounts/environment catalog, Terraform versions, team permissions, VCS configuration, and
required checks there when adding workspaces. Alternatively, move those values into declared Terraform inputs or a generate
template that emits the same object shape. Do not infer that the presence of an Atmos stack creates a TFE workspace. This is
explicit Terraform management, separate from using a remote backend.
Common Questions
These optional migration scenarios cover existing repository customizations as well as generated Terraform. They do not extend the upstream Fogg feature assessment above.
Cutover Checklist
- Validate stacks and inspect each resolved component path, workspace, dependencies, and tool pin.
- Confirm generated backends select the original state and provider overrides preserve aliases, account restrictions, and tags. Verify module addresses and output names/sensitivity are unchanged.
- Compare baseline and Atmos plans under identical credentials, inputs, versions, and refresh conditions. Investigate drift rather than accepting unexpected migration changes.
- Exercise checks in CI, including a failing check and a plan with changes. Check dependency order and bootstrap producers before consumers that read outputs.
- Switch one state at a time to its Atmos apply pipeline. Disable competing writes through the former pipeline. Remove Fogg/Make setup only after inventorying custom tasks and plugin paths.
- Remove
fogg.yml, Fogg runtime dependencies, and obsolete generated-file notices once all roots and automation are owned by Terraform/Atmos. Retain the maintained Terraform files.
Rollback means restoring the prior configuration and execution pipeline against the same state, with only one writer enabled. Remove only Atmos-generated override files introduced during the cutover if they would affect the restored configuration. Do not restore an old state snapshot over newer applies merely to change orchestration tools.
Stack resolution and local file generation can be checked without cloud access. AWS plans, cross-account authentication, custom artifact downloads, and Terraform Cloud/Enterprise runs must be verified against your own environment. The illustrative account names, backend locations, module interfaces, and artifact URLs in this guide are not a deployed reference environment.