Skip to main content

Migrating Tasks from Taskfile.yml

Use Atmos as a task runner for the build, test, lint, release, and maintenance tasks your team currently runs with Task (go-task). This guide maps Taskfile tasks, dependencies, variables, and freshness checks to Atmos custom commands and workflows.

Using an AI Coding Assistant?

Install the atmos-migration skill so Claude Code, Cursor, GitHub Copilot, and other AI coding assistants can apply this guide directly to your repository:

atmos ai skill install atmos-migration

See AI Agent Skills for details.

Start with Your Existing Tasks

Task-runner adoption starts with an atmos.yaml file in your project. Stacks, components, Terraform, and cloud credentials are optional; none are required for the tasks in this guide. The examples use a Go application, but the same mapping works for other languages, documentation builds, release scripts, and local development tools.

You can migrate one task at a time. To start, expose an existing task through Atmos:

atmos.yaml
commands:
- name: build
description: Build the application
steps:
- type: shell
command: task build

Run atmos build from the project directory. When you are ready, replace task build with the task's own commands. The examples below show that next step. They assume your existing application, scripts, and required tools are available.

Key Concepts at a Glance

Taskfile.yml conceptAtmos equivalent
desc:Command description:
cmds: (list of shell commands)steps: (type: shell, or type: atmos for any atmos command)
deps: (runs at the same time by default)Command dependencies.commands, concurrent by default
vars: / env:Command flags: (with default:) / env: map
sources: / generates: (freshness check)Step inputs.sources / artifacts.paths
includes: (multi-file composition)Auto-discovered atmos.d/*.yaml, or separate workflow files
internal: true taskCommand internal: true (weaker guarantee -- see below)

deps: Becomes dependencies.commands

Task runs deps: at the same time by default. Atmos custom-command and workflow steps, in contrast, run one after another by default — so a deps: entry is not a step and never becomes one. It maps to the command-level dependencies.commands field, which resolves through the same DAG scheduler as parallel/matrix needs: and runs concurrently by default — matching Task's deps: behavior directly, not working around it:

tasks:
deploy:
desc: Deploy the application to the selected environment
vars:
ENV: '{{.ENV | default "dev"}}'
deps: [test, lint]
cmds:
- ./scripts/deploy.sh "{{.ENV}}"

The Task ENV variable becomes the Atmos --env flag. atmos deploy passes the default dev to your script; atmos deploy --env staging passes staging. The matching Task commands are task deploy and task deploy ENV=staging.

dependencies.commands also matches a behavior Task itself has that a hand-rolled parallel step does not: if two commands both depend on the same one — for example both test and lint depending on build — Atmos runs build exactly once and dedups it, the same as Task's own deps: graph. A parallel step calling atmos build from two different places would run it twice. If one dependency itself depends on another (lint depends on build, and deploy depends on test and lint), declare that directly on lint's own dependencies.commands — the scheduler resolves the whole transitive graph itself, still deduping build to a single run.

Reach for a parallel step instead of dependencies.commands only for concurrency inside a single command's own steps, not between named commands — for example, running several shell commands side by side that were never their own Task tasks to begin with.

sources:/generates: Becomes inputs/artifacts

Task skips a task's cmds: when its sources: files match its generates: outputs, checked by default with a content hash (Task also supports method: timestamp for an mtime-based check). The step-level inputs.sources and artifacts.paths fields are the direct match, with the same checksum-by-default/timestamp-as-an-option choice:

commands:
- name: build
description: Compile the deployable artifact
steps:
- type: shell
command: go build -o bin/handler ./cmd/handler
inputs:
sources: ["cmd/**/*.go"]
artifacts:
paths: ["bin/handler"]

With no explicit when:, declaring inputs/artifacts on a step is enough — it implicitly means when: checksum.changed, and the step is skipped when the hash of the matched source files matches the hash recorded after the last successful run. Run the same command twice in a row and the second run skips the build step; edit a matched source file and the next run executes it again. See inputs for the full field reference, including the timestamp.changed fact for an mtime-based check matching Task's method: timestamp mode. require/assert is a different, older step type — it only checks that a file, tool, or directory exists, not whether it is fresh, so it does not replace inputs/artifacts.

The scope is different, too: Task's sources:/generates: gates the task's entire cmds: list, but inputs/artifacts are declared per step — skipping one step does not stop later steps in the same command from running. If a task has more than one cmds: entry and the freshness decision must gate all of them together, combine them into a single shell/script step rather than spreading inputs/artifacts across several migrated steps.

internal: true Tasks

Set internal: true on the custom command. The command still runs (atmos <name> ..., or as a default: target, or from another command's steps), but it's excluded from atmos --help listings and completion suggestions — matching how an internal: true task disappears from task --list.

This is not the same guarantee Task gives you. In Task, internal: true also blocks direct invocation — running an internal task by name from the CLI fails with an error; it is only reachable from another task's deps:/cmds:. Atmos's internal: true maps to Cobra's Hidden field: it removes the command from help output and shell completion, but the command still executes if invoked directly (atmos <name> ...). If the user has a Task helper that must genuinely be unreachable on its own — not just hidden from discovery — migrating it straight to internal: true does not reproduce that constraint. Review any such helpers and, if direct invocation must be blocked, add a guard inside the command (for example a precondition or an early check on an expected caller-only flag/env var) rather than relying on internal: true alone. Reserve inlining the logic into a caller's step for a helper that's genuinely single-caller and has no reason to be invoked on its own.

Before and After

version: '3'

tasks:
build:
desc: Compile the deployable artifact
cmds:
- go build -o bin/handler ./cmd/handler
sources:
- cmd/**/*.go
generates:
- bin/handler

test:
desc: Run unit tests
deps: [build]
cmds:
- go test ./...

lint:
desc: Run static analysis
cmds:
- go vet ./...
atmos.yaml
commands:
- name: deploy
description: Deploy the application to the selected environment (default dev)
flags:
- name: env
default: "dev"
dependencies:
commands: [test, lint]
steps:
- type: shell
command: ./scripts/deploy.sh "{{ .Flags.env }}"
# Use the default environment (dev)
atmos deploy

# Override the environment
atmos deploy --env staging

includes: Splits into Commands and Workflows

Task's includes: field joins several Taskfiles into one. Atmos has two matching methods. Choose the one that fits what you are splitting:

  • To split command definitions across files, put them in files such as atmos.d/commands.yaml or .atmos.d/commands.yaml. Atmos auto-discovers atmos.d//.atmos.d/ in the config directory (and, as a lower-priority fallback, at the git/worktree root) -- no import: entry is needed for this specific location. Use import: only when splitting across a directory Atmos does not auto-discover. See Imports.
  • To split multi-step chains, use separate workflow files. Atmos workflows already live one file per purpose, under workflows.base_path. Unlike atmos.d/.atmos.d, there is no default for workflows.base_path -- add it explicitly (for example workflows.base_path: "stacks/workflows") the first time your migration reaches a workflow, or atmos workflow <name> fails with 'workflows.base_path' must be configured in 'atmos.yaml'.

What You Gain

  • Discoverable commands. Add descriptions, typed flags, defaults, and per-command help for your team's tasks with atmos --help and atmos <command> --help.
  • Typed steps. Combine shell commands with HTTP requests, containers, parallel execution, matrices, and other step types.
  • Interactive workflows. Add prompts, confirmations, and structured output where your tasks need them.
  • Tool dependencies. Declare required CLI versions with dependencies.tools so Atmos installs them before running the command. See Toolchain Configuration.

Migration Checklist

  • List every task. Mark each one as independent, or part of a deps: chain
  • Turn desc:/cmds: into command description:/steps:
  • Turn any deps: chain into dependencies.commands, to keep Task's concurrent-by-default behavior
  • Turn vars:/env: into command flags: (with defaults) and env: maps
  • Turn sources:/generates: into step inputs.sources/artifacts.paths -- combine multiple cmds: entries into one step if the freshness decision must gate all of them together
  • Split includes: into auto-discovered atmos.d/*.yaml files, separate workflow files, or both
  • Mark internal: true tasks as internal: true custom commands

Common Questions