Task: A Modern, Friendly Make Replacement

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

  • desc shows up in task --list output, making self‑documentation effortless.
  • silent: true keeps 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:ready keeps internals tidy and discoverable.
  • Robust readiness with curl --retry avoids race conditions.
  • deps run concurrently by default; tunnel won’t start until serve:ready passes.
  • output: prefixed makes 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: true keeps the task running and rebuilds on file changes.
  • sources and generates enable smart change detection and caching.
  • You can now live‑edit post.adoc and 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, cmds arrays, 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.yml and add your first task with a desc.
  • Run task to list what’s available; use task <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.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *