Task Runner (Taskfile)

Task (go-task) cheatsheet — modern task runner with Taskfile.yml. task build, task test, task deploy. Define tasks with dependencies, variables, and env. Simpler make alternative.

8 min read

What it is

Task is a simple, dependency-based command runner that helps you automate common development tasks like building, testing, and deploying.

Installation

Linux

# Using apt (Debian/Ubuntu)
sudo apt update && sudo apt install task-yaml

# Using dnf (Fedora)
sudo dnf install task-yaml

# Using pacman (Arch Linux)
sudo pacman -S task-yaml

# Using go (if you have Go installed)
go install github.com/go-task/task/v3/cmd/task@latest

macOS

# Using Homebrew
brew install go-task/tap/go-task

Windows

# Using Chocolatey
choco install go-task

# Using Scoop
scoop install go-task

# Using winget
winget install go-task

Core Concepts

  • Taskfile.yml: The central configuration file where you define your tasks. It’s a YAML file that resides in your project’s root directory.
  • Task: A named command or a sequence of commands defined within the Taskfile.yml. Tasks can depend on other tasks.
  • Dependencies: Tasks can depend on other tasks. When a task is run, its dependencies are executed first. This allows for building complex workflows.
  • Recipes: The actual commands to be executed within a task.
  • Variables: You can define and use variables within your Taskfile.yml for reusability and flexibility.
  • Environments: You can set environment variables for specific tasks or globally.
  • Generators: Task can generate files based on templates.

Commands / Usage

Defining Tasks in Taskfile.yml

A basic Taskfile.yml looks like this:

version: '3'

tasks:
  hello:
    desc: Prints a greeting
    cmds:
      - echo "Hello, Task!"

  build:
    desc: Builds the project
    deps:
      - clean
    cmds:
      - go build -o myapp ./cmd/myapp
    vars:
      GOOS: linux
      GOARCH: amd64

  clean:
    desc: Cleans the build directory
    cmds:
      - rm -rf bin/

  test:
    desc: Runs tests
    deps:
      - build
    cmds:
      - go test ./...

  deploy:
    desc: Deploys the application
    cmds:
      - echo "Deploying..."
      - ./myapp --deploy
    vars:
      APP_ENV: production

Running Tasks

  • Run a specific task:

    task hello
    # Output: Hello, Task!
    
  • Run a task with dependencies:

    task test
    # Output:
    # rm -rf bin/
    # go build -o myapp ./cmd/myapp
    # go test ./...
    

    (The clean and build tasks will run before test because they are listed as dependencies.)

  • Run multiple tasks:

    task build test
    # Output:
    # rm -rf bin/
    # go build -o myapp ./cmd/myapp
    # go test ./...
    
  • Run tasks with environment variables:

    task -e APP_ENV=staging deploy
    # Output:
    # Deploying...
    # ./myapp --deploy
    

    (This will run the deploy task with APP_ENV set to staging for that specific run.)

  • List available tasks:

    task --list
    # Or
    task -l
    

    (This will show a list of tasks defined in your Taskfile.yml with their descriptions.)

  • Show task details (description, dependencies, commands):

    task --summary build
    # Output:
    # Task: build
    # Description: Builds the project
    # Dependencies:
    #   - clean
    # Commands:
    #   - go build -o myapp ./cmd/myapp
    # Variables:
    #   GOOS: linux
    #   GOARCH: amd64
    

Taskfile Configuration Options

  • version: Specifies the Taskfile schema version (e.g., '3').
  • env: Global environment variables for all tasks.
    version: '3'
    env:
      BUILD_DIR: bin
    tasks:
      build:
        cmds:
    

{% raw %} - echo "Building in {{.BUILD_DIR}}" {% endraw %} ```

  • vars: Global variables for all tasks.
    version: '3'
    vars:
      APP_NAME: my-awesome-app
    tasks:
      run:
        cmds:
    

{% raw %} - ./{{.APP_NAME}} {% endraw %} ```

  • ignore_error: If true, Task will not exit if a command fails.
    version: '3'
    tasks:
      cleanup:
        ignore_error: true
        cmds:
          - rm -f temp.log
          - rm -f non_existent_file.txt # This command will fail, but task will continue
    
  • silent: If true, Task will not print commands before executing them.
    version: '3'
    tasks:
      quiet_build:
        silent: true
        cmds:
          - echo "Building quietly..."
    
  • skip_if: Skip task execution if the condition is true.
    version: '3'
    tasks:
      deploy_prod:
        skip_if: 'eq(env.BRANCH, "main")' # Skip if BRANCH env var is "main"
        cmds:
          - echo "Deploying to production..."
    
  • preconditions: Tasks that must pass before the current task can run. Similar to deps but can include conditions.
    version: '3'
    tasks:
      deploy:
        preconditions:
          - "command(git diff --quiet HEAD)" # Ensure working directory is clean
        cmds:
          - echo "Deploying..."
    
  • sources: A list of files or directories that, if changed, will cause the task to be re-run (useful for watchers).
    version: '3'
    tasks:
      serve:
        sources:
          - src/**/*.go
          - static/**
        cmds:
          - go run cmd/server/main.go
    
  • generates: A list of files that this task is expected to create. If these files exist, the task might be skipped (depending on other conditions).
    version: '3'
    tasks:
      generate_config:
        generates:
          - config.yaml
        cmds:
          - ./config_generator > config.yaml
    
  • cmds: The actual commands to execute.
    • Shell command execution: By default, commands are executed in the shell.
    • sh: ...: Explicitly run commands using sh.
      tasks:
        run_script:
          cmds:
            - sh: |
                echo "Starting..."
                ./my_script.sh
                echo "Done."
      
    • script: ...: A more idiomatic way to define multi-line shell scripts.
      tasks:
        run_script:
          script: |
            echo "Starting..."
            ./my_script.sh
            echo "Done."
      

Variables and Templating

Task uses Go’s text/template package for templating.

{% raw %}

  • Accessing variables: Use {{.VAR_NAME}}. {% endraw %}
    version: '3'
    vars:
      GREETING: Hello
      TARGET: World
    tasks:
      greet:
        cmds:
    

{% raw %} - echo "{{.GREETING}}, {{.TARGET}}!" {% endraw %} bash task greet # Output: Hello, World! ```

{% raw %}

  • Environment variables: Access using {{.ENV.VAR_NAME}}. {% endraw %}
    version: '3'
    tasks:
      show_path:
        cmds:
    

{% raw %} - echo "Your PATH is: {{.ENV.PATH}}" {% endraw %} ```

{% raw %}

  • Task variables: Access using {{.TASK_VAR_NAME}}. {% endraw %}
    version: '3'
    tasks:
      build:
        vars:
          GOOS: linux
          GOARCH: amd64
        cmds:
    

{% raw %} - echo "Building for {{.GOOS}}-{{.GOARCH}}" {% endraw %} ```

  • Built-in variables: {% raw %}

    • {{.ROOT}}: The root directory of the Taskfile. {% endraw %} {% raw %}
    • {{.CURRENT}}: The directory where the task is being run from. {% endraw %} {% raw %}
    • {{.TASK}}: The name of the current task. {% endraw %} {% raw %}
    • {{.PREV_TASK}}: The name of the previously executed task. {% endraw %}
  • Functions: Available functions include eq, ne, lt, le, gt, ge, contains, default, env, getopt, glob, include, join, k8s, lower, print, printf, quote, repeat, replace, split, toToml, toYaml, upper.

    version: '3'
    tasks:
      conditional_run:
        cmds:
    

{% raw %} - '{{if eq (env "CI") "true"}}echo "Running in CI"{{end}}' {% endraw %} {% raw %} - '{{if not (eq (default 0 (getopt "port" "8080")) 8080)}}echo "Using custom port: {{getopt "port" "8080"}}"{{end}}' {% endraw %} ```

Task Arguments (getopt)

Tasks can accept arguments.

version: '3'
tasks:
  greet:
    cmds:
{% raw %}
      - 'echo "Hello, {{.GETOPT.name | default "World"}}!"'
{% endraw %}
task greet --name Alice
# Output: Hello, Alice!

task greet
# Output: Hello, World!

You can also define argument constraints and descriptions.

version: '3'
tasks:
  greet:
    deps:
      - validate_name
    cmds:
{% raw %}
      - 'echo "Hello, {{.GETOPT.name}}!"'
{% endraw %}

  validate_name:
    internal: true # Hide from `task --list`
    cmds:
{% raw %}
      - '{{if not .GETOPT.name}}echo "Error: --name argument is required." && exit 1{{end}}'
{% endraw %}

Including Other Taskfiles

You can split your tasks into multiple files.

Taskfile.yml:

version: '3'
includes:
  build: ./build/Taskfile.yml
  test: ./test/Taskfile.yml

build/Taskfile.yml:

version: '3'
tasks:
  build_app:
    cmds:
      - echo "Building the app..."

test/Taskfile.yml:

version: '3'
tasks:
  run_tests:
    cmds:
      - echo "Running tests..."

Then you can run them like:

task build:build_app
task test:run_tests

Generators

Task can generate files.

version: '3'
tasks:
  generate_readme:
    desc: Generates a README.md from a template
    generates:
      - README.md
    vars:
      APP_VERSION: 1.0.0
    cmds:
      - |
{% raw %}
        echo "# My App v{{.APP_VERSION}}" > README.md
{% endraw %}
        echo "" >> README.md
        echo "This is a great application." >> README.md

Watchers

The sources and interval options can be used to create watchers.

version: '3'
tasks:
  dev:
    desc: Runs the development server and watches for changes
    sources:
      - src/**/*.go
      - static/**
    interval: 500ms # Check for changes every 500 milliseconds
    cmds:
      - go run cmd/server/main.go

Run task dev. Task will start the command and re-run it if any file in src/ or static/ changes.

Common Patterns

  • Build, Test, Run Workflow:

    version: '3'
    tasks:
      build:
        cmds:
          - go build -o myapp ./cmd/myapp
      test:
        deps:
          - build
        cmds:
          - go test ./...
      run:
        deps:
          - build
        cmds:
          - ./myapp
      all:
        deps:
          - build
          - test
    
    task all # Runs build and test
    task run # Runs build and then the app
    
  • Cleaning Build Artifacts:

    version: '3'
    tasks:
      clean:
        cmds:
          - rm -rf bin/ dist/ *.o
    
    task clean
    
  • Docker Builds and Runs:

    version: '3'
    tasks:
      docker-build:
        cmds:
          - docker build -t myapp:latest .
      docker-run:
        deps:
          - docker-build
        cmds:
          - docker run -p 8080:80 myapp:latest
      docker-compose-up:
        cmds:
          - docker-compose up -d
      docker-compose-down:
        cmds:
          - docker-compose down
    
    task docker-build
    task docker-run
    task docker-compose-up
    
  • Linting and Formatting:

    version: '3'
    tasks:
      fmt:
        cmds:
          - go fmt ./...
      lint:
        cmds:
          - golangci-lint run
      format-and-lint:
        deps:
          - fmt
          - lint
    
    task format-and-lint
    
  • Conditional Execution based on Environment:

    version: '3'
    tasks:
      deploy:
        cmds:
    

{% raw %} - '{{if eq (env "CI") "true"}}echo "Deploying from CI…"{{else}}echo "Deploying locally…"{{end}}' {% endraw %} ```

  • Passing Arguments to Tasks:
    version: '3'
    tasks:
      greet:
        cmds:
    

{% raw %} - 'echo "Hello, {{.GETOPT.name | default "there"}}!"' {% endraw %} bash task greet --name "Developer" task greet ```

Gotchas

  • Working Directory: By default, Task runs commands from the directory containing the Taskfile.yml. If you need to change this for a specific task, you can use the dir option:

    version: '3'
    tasks:
      run_in_subdir:
        dir: ./scripts
        cmds:
          - ./my_script.sh
    
  • Shell Interpretation: Commands are executed in a shell. Be mindful of shell quoting and escaping rules. For complex scripts, using the script: block is often cleaner.

  • Default Task: If you have a task named default in your Taskfile.yml, it will be executed when you run task without any arguments.

    version: '3'
    tasks:
      default:
        cmds:
          - echo "This is the default task."
    
    task
    # Output: This is the default task.
    
  • Variable Precedence: Variables defined closer to the task (e.g., in the task’s vars block) override globally defined variables. Environment variables passed via -e override task variables.

  • Error Handling: By default, Task stops execution if any command returns a non-zero exit code. Use ignore_error: true on a task to prevent this.

  • internal: true: Tasks marked as internal: true are not shown in task --list and cannot be directly invoked by name. They are typically used as helper tasks or preconditions.