Article summary
Every project is better with great developer automation. If you’ve ever tried to build up a nice toolset using shell scripts, Makefiles, package.json scripts, or the like, you’ve likely run into the pain of keeping things organized, dependencies between tasks, or arcane Makefile syntax.
Task: A Modern, Friendly Make Replacement
Task is a tiny, cross‑platform task runner that makes those dev tools simpler, more reliable, and more pleasant to work with. It’s written in Go and compiled to a single binary, so installation is a breeze. It’s declarative, understands dependencies, supports watch mode, and plays nicely with the tools you already use.
You can find documentation here.
Why Task?
- Cross‑platform (macOS, Linux, Windows)
- Declarative YAML with a public schema
- Built‑in watch mode and fast, incremental runs
- Great DX: nice output, env vars, and easy composition
Here are a few patterns I’ve used recently in my work or side projects.
List your tasks (the “default” task).
The default task is a convenient, documented entry point. Running task will execute it.
version: "3"
tasks:
default:
silent: true
desc: List all tasks with descriptions
cmd: task --list-all
Tips
descshows up intask --listoutput, making self‑documentation effortless.silent: truekeeps logs clean when your command already prints its own output.
Example output
> task
task: Available tasks for this project:
* default: List all tasks with descriptions
* dev: Start the development server and expose it via Tailscale
* post:copy: Copy the production ready HTML to the clipboard
* post:dev: Build the blog post
Composable Services with Deps and Readiness Checks
Task makes it easy to orchestrate multi‑step dev flows. Here’s a static server plus a readiness probe and a public tunnel.
# https://taskfile.dev/schema.json
version: "3"
output: prefixed
silent: true
tasks:
serve:
internal: true
cmd: python3 -m http.server ${PORT:-5173}
serve:ready:
internal: true
cmd: |
curl -fsS --retry 30 --retry-connrefused --retry-delay 1 \
--retry-max-time 30 --connect-timeout 1 \
http://localhost:${PORT:-5173}/ >/dev/null 2>&1
tunnel:
internal: true
deps: [serve:ready]
cmd: tailscale funnel ${PORT:-5173}
dev:
desc: Start the development server and expose it via Tailscale
deps: [serve, tunnel]
Notes
- Namespacing via
serve:readykeeps internals tidy and discoverable. - Robust readiness with
curl --retryavoids race conditions. depsrun concurrently by default;tunnelwon’t start untilserve:readypasses.output: prefixedmakes logs easy to scan when multiple tasks run at once.${PORT:-5173}shows off easy env var defaults, no extra YAML tricks required.
Run it
> task dev
[serve] ::1 - - [24/Sep/2025 16:05:34] "GET / HTTP/1.1" 200 -
[tunnel] Available on the internet:
[tunnel]
[tunnel] https://computer.tailnet.ts.net/
[tunnel] |-- proxy http://127.0.0.1:5173
[tunnel]
[tunnel] Press Ctrl+C to exit.
Stop with Ctrl‑C; Task will propagate signals cleanly to child processes.
Fast docs with watch + incremental builds
Task’s watch, sources, and generates make rebuilding only when needed trivial. Here we turn an AsciiDoc post into HTML.
tasks:
post:dev:
watch: true
sources:
- Taskfile.yml
- post.adoc
generates: [.public/post.html]
desc: Build the blog post
cmd: asciidoctor -o .public/post.html post.adoc
post:copy:
desc: Copy the production ready HTML to the clipboard
silent: true
cmd: asciidoctor -o - -e post.adoc | pbcopy
Notes
watch: truekeeps the task running and rebuilds on file changes.sourcesandgeneratesenable smart change detection and caching.- You can now live‑edit
post.adocand instantly refresh.public/post.html.
Run it
> task post:dev
task: Started watching for tasks: post:dev
task: [post:dev] asciidoctor -o .public/post.html post.adoc
task: task "post:dev" finished running
We’ve only scratched the surface.
Task ships with a lot more power than the examples above:
- Platform‑specific tasks using
platforms: [darwin, linux, windows]. - Preconditions to fail fast with friendly errors via
preconditions. - Variables and templates (
vars, env vars, and dotenv files). - Includes to split large Taskfiles and share tasks across repos.
- Rich execution control:
dir,env,cmdsarrays, and more.
Example (platforms + preconditions):
tasks:
docker:build:
desc: Build Docker image
platforms: [linux, darwin]
preconditions:
- sh: command -v docker >/dev/null 2>&1
msg: "Docker is required to build images"
cmds:
- docker build -t my/app .
Get started.
- Install Task: macOS
brew install go-task; for other platforms see the docs. - Initialize a
Taskfile.ymland add your first task with adesc. - Run
taskto list what’s available; usetask <name>to run any task.
That’s it. Replace ad‑hoc shell scripts and brittle Makefiles with a single, readable Taskfile. Your future self—and your teammates—will thank you.