Ben Schenk

Web Developer

  • Home
  • About Me
  • Projects
  • Blog

© 2025 Ben Schenk

Banner image for the post.

5 Reasons You Should Build with Task

buildtoolsconfigdxdevtools
23 February 2026

What is Task?

Task is a build tool similar to Make or just, which allows you to write project-specific build and dev commands and organise your workflow.

Ever been working on a project with tens or even hundreds of services that need to run locally and all have separate commands to run? Task makes it easier to manage and run them all at once.

This article is up-to-date with Task v3.47.0. If you're using a different version, the features mentioned in this article may not be available.

The Basics

Task operates using YAML files that define the tasks for your project (ie. taskfile.yml). Inside, you define tasks that run sequentially using the following syntax:

taskfile.yml
Copy
1version: "3"
2
3tasks:
4 build:
5 desc: Run the build process for the project.
6 cmds:
7 # Insert commands here...
8 - bun run test
9 - bun run lint
10 - docker build -t my-app .

Why should you use Task?

5. Environment Variable Handling

Task provides a number of ways to handle environment variables in your tasks.

The most primitive way to handle environment variables is to use the env key to define task-specific environment variables.

taskfile.yml
Copy
1
2tasks:
3 build:
4 desc: Run the build process for the project.
5 env:
6 - APP_ENV=development
7 cmds:
8 # ...

If certain environment variables are required for all tasks, you can set them in the env key at the top level of the taskfile.

taskfile.yml
Copy
1version: "3"
2
3env:
4 - API_KEY=sk_test_1234567890

You can also use the dotenv key to load environment variables from a .env file.

taskfile.yml
Copy
1version: "3"
2
3dotenv:
4 - .env.local # Highest priority
5 - .env # Lowest priority

4. Multi-line Commands

By default, each command in a task is executed in a new shell. If you need to write a command that requires multiple lines, you can use the | character to write a multi-line command.

taskfile.yml
Copy
1tasks:
2 build:
3 desc: Run the build process for the project.
4 cmds:
5 - |
6 if [ "$APP_ENV" = "development" ]; then
7 bun run test
8 else
9 bun run build
10 fi

Perfect for commands complicated enough to require multiple lines but not complicated enough to require a script file.

3. Global and Required Variables

Task allows you to define global variables for your tasks. They are defined at the top level of the taskfile.

taskfile.yml
Copy
1version: "3"
2
3vars:
4 APP_NAME: myapp
5 VERSION: 1.0.0

If a task requires a variable, you can define it as required by using the requires key. The best part about this is that you can also validate the variable against a type. See the example below:

taskfile.yml
Copy
1tasks:
2 build:
3 desc: Run the build process for the project.
4 requires:
5 vars:
6 - name: ENV
7 enum: [dev, beta, prod]

If ENV is not set, or not in dev, beta, or prod, Task will throw an error and the task will not run. This is the default behaviour, but you can also change the config to prompt the user to enter the value of the variable if it is not set.

Want to pass the variable in at runtime? Do this:

taskfile.yml
Copy
1version: "3"
2
3vars:
4 END_TO_END: '{{.END_TO_END | default "false"}}'
5
6tasks:
7 tet:
8 desc: Run the test suite for the project.
9 env:
10 END_TO_END: '{{.END_TO_END}}'
11 cmds:
12 - bun run test
13 requires:
14 vars:
15 - name: END_TO_END

Then when running the task, you can pass the variable in at runtime: ie. task build SEND_MAIL=true.

Note also how I can use the | default "false" syntax to set a default value if the variable is not set.

2. Control Flow

In a recent update, Task added support for control flow.

At the command level, a particular command that should only run if a particular condition is satisfied can be written like this:

taskfile.yml
Copy
1tasks:
2 build:
3 desc: Run the build process for the project.
4 cmds:
5 - cmd: bun run build
6 if: '[ "$ENV" = "production" ]'

For example, a use case I often find for this is setting and using runtime arguments:

taskfile.yml
Copy
1tasks:
2 build:
3 desc: Run the dev server for the project.
4 cmds:
5 - cmd: bun run dev --port {{.PORT}}
6 if: '{{ne .PORT 3000}}'

If statements can also be set at the task level if a particular task should only run when a condition is satisfied:

taskfile.yml
Copy
1tasks:
2 install:
3 desc: Install the dependencies for the project.
4 if: '{{eq .INSTALL_DEPENDENCIES "true"}}'
5 cmds:
6 - cmd: bun install

The update also provided support for loops; I personally haven't found a use case for this yet, but it's a feature that is available if you need it.

taskfile.yml
Copy
1tasks:
2 process-items:
3 cmds:
4 - for: ['a', 'b', 'c']
5 cmd: echo "processing {{.ITEM}}"
6 if: '[ "{{.ITEM}}" != "b" ]'

1. OS-Specific Taskfiles

This is one of the coolest features of Task.

When working in a development team, you'll often have team members working across different operating systems. Or, you may have a staging environment that uses a different architecture to your production environment.

Never fear! Task allows you to define OS or architecture specific taskfiles that override default tasks.

For example, you may want to use a different package manager for Linux and macOS. You can define a taskfile for each OS and include the common tasks in the main taskfile.

To identify a taskfile as OS specific, alter the name of the taskfile to include the OS name, such as taskfile_darwin.yml or taskfile_windows.yml

So that task can find the correct taskfile for the OS, you can use the include key to include the OS-specific taskfiles in a root taskfile.

I usually set up my directory like this:

~
Copy
1├── infra
2│   └──dev
3│ ├── taskfile_darwin.yml
4│ └── taskfile_windows.yml
5└── taskfile.yml

Then in the root taskfile:

taskfile.yml
Copy
1version: "3"
2
3includes:
4 os:
5 taskfile: ./infra/dev/taskfile_{{OS}}.yml
6 flatten: true

I recommend adding flatten: true to your include statements for your OS taskfiles. This abstracts the OS-specific nature of the task to the user running the command. For example, you can have each OS specific taskfile define its own install command, and then regardless of the OS, the correct OS-specific install command will be run. Running task -l will show only the correct install task for the OS.

If you only have a specific task that is OS-specific, and don't want to create a new taskfile for it, you can use the platforms key to limit the platforms the task can run on.

taskfile.yml
Copy
1tasks:
2 build:
3 desc: Run the build process for the project.
4 platforms: [linux]

Conclusion

Task is a powerful build tool that allows you to write project-specific build and dev commands and organise your workflow. It is a great alternative to Make or just, and a great tool to have in your devtools.

If you're interested in learning more about Task, check out the Task documentation.

Banner by Diane Picchiottino on Unsplash