Task
Task runner and build tool simpler than Make
Task is a task runner and build tool that uses a simple YAML file to define and run repeatable commands across projects. It is self-hosted as a single cross-platform binary.
Key features
- YAML task definitions
- Cross-platform binary
- Dependency tracking
- Variable templating
Pros & cons
Strengths
- Single static binary
- Simple YAML syntax
- Fully cross-platform
Trade-offs
- Less ubiquitous than Make
- Complex logic gets awkward
Task replaces
Last reviewed Aug 26, 2026 · 812 words
Every homelab accumulates the same 6 scripts: bring the stack up, pull updates, back up the volumes, restore one, tail the logs, prune what is dangling. Task turns those into one Taskfile.yml per host, discoverable with task --list, runnable with task backup, and delivered as a single static Go binary with zero dependencies on the target machine. It is not a server and there is nothing to host; it is the thing that makes the servers you do host easier to operate.
A homelab Taskfile that earns its place
Here is a real one, trimmed, for a Docker Compose host:
version: '3'
dotenv: ['.env']
vars:
BACKUP_DIR: /srv/backups
tasks:
up:
desc: Start the stack
cmds:
- docker compose up -d
update:
desc: Pull new images and restart what changed
cmds:
- docker compose pull
- docker compose up -d
- docker image prune -f
backup:
desc: Snapshot volumes to {{.BACKUP_DIR}}
cmds:
- restic backup /srv/app-data --tag compose
logs:
desc: Follow logs for one service
cmds:
- docker compose logs -f {{.CLI_ARGS}}
task logs -- immich passes the service name through CLI_ARGS, desc lines are what task --list prints, and dotenv loads your secrets without you exporting them. That is the whole learning curve for 80 percent of use.
Up-to-date checks and dependencies save real minutes
Task's sources and generates keys give you Make's incremental behaviour without Make's tab-sensitivity: if no listed source file changed since the last run, the task is skipped. On a homelab that turns "rebuild the custom image" into a 0-second no-op most of the time. deps runs prerequisites first, and in parallel by default, so task deploy with deps: [lint, build] does both before the deploy step. task --watch reruns on file change, which is handy while editing a Caddyfile or a Compose file you want validated on each save.
Task against Make and just
| Task | Make | just | |
|---|---|---|---|
| Syntax | YAML | Makefile, tabs matter | Makefile-like, no tabs rule |
| Installed everywhere already | No | Yes on Linux and macOS | No |
| Incremental via file timestamps | Yes | Yes, it is the whole point | No |
| Windows support | First-class | Painful | Good |
| Templating and variables | Go templates | Make's own language | Its own, simpler |
Make is ubiquitous and that matters: a Makefile runs on any box you SSH into. Task and just both need to be installed first, which is the catalogue's "less ubiquitous" drawback in one line. Between the two, just has the nicer syntax for plain command lists and Task has incremental builds, includes and a proper deps model. I use Task where the build is real and just where it is a list of aliases.
Where the YAML gets in the way
Complex logic in YAML is miserable, and Task does not pretend otherwise. Multi-line shell with quotes, conditionals and loops ends up with | blocks and Go template escaping that is harder to read than the bash it replaced. The fix is simple: once a task exceeds about 10 lines of shell, move it to scripts/thing.sh and have the task call it. Task then stays what it is good at, a discoverable menu with variables and ordering, and the logic lives in a language built for it. The same applies to preconditions and status checks: use them for "is Docker running", not for business rules.
Install once, run everywhere
The install script drops the binary where you tell it, and it is around 10 MB:
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
It is also in Homebrew, Snap, most distro repos, and go install github.com/go-task/task/v3/cmd/task@latest if you have Go. Two features matter for a fleet: task -g runs a global Taskfile from your home directory, so task -g update-all can SSH into each host, and includes lets one Taskfile pull in another, so a shared common.yml in a Git repository gives every host the same backup and update tasks with per-host variables. With a Forgejo instance holding that repo, a new box is bootstrapped with one clone and one task setup.
What I'd do
Install Task on every host, commit a Taskfile.yml beside each docker-compose.yml, and give each one the same 5 tasks: up, update, backup, restore and logs. Push complex steps into scripts the tasks call. Keep Make for anything that has to run on a machine you do not control, and keep just out of it unless you already like it. The win is not speed, it is that 6 months from now task --list tells you how this host is operated without opening a single script. The full range of options in the dev-tools category is wide, but this one costs 10 minutes to adopt and pays back weekly.
Compare Task
1 head-to-head comparisons.
Similar developer tools & git apps
Excalidraw
Developer Tools & GitVirtual hand-drawn style whiteboard
Replaces Miro
lazygit
Developer Tools & GitSimple terminal UI for Git commands
Replaces GitKraken, Sourcetree
Hoppscotch
Developer Tools & GitOpen-source API development ecosystem
Replaces Postman, Insomnia
json-server
Developer Tools & GitFull fake REST API from a JSON file in seconds
Replaces Mockoon, Postman Mock
Strapi
Developer Tools & GitLeading open-source headless CMS
Replaces Contentful
NocoDB
Developer Tools & GitOpen-source Airtable alternative
Replaces Airtable