Back to articles
3 min read

Taskfile for project automation

Task runner with built-in docs, descriptions, and YAML syntax.

DevExAutomationTools

Makefiles work. I’ve used them for years. But the DevEx is stuck in 1976.

Taskfile gives you the same automation with task descriptions, built-in help, and YAML instead of tab hell. It’s what Makefiles should have evolved into.

What Makefiles get wrong

Run make in most projects and you get an error or it builds something random. Run make help and nothing happens unless someone wrote a custom target with grep and awk.

Task descriptions? You write them in comments and hope someone reads the file. Dependencies between tasks? They work, but the syntax looks like line noise.

Makefiles are great for what they do. But discoverability is terrible.

Taskfile fixes the DevEx problems

Run task without arguments and you get a list of available tasks with descriptions. Run task --list and you see everything organized.

Each task has a description. You know what it does before running it.

version: '3'

tasks:
  dev:
    desc: Start development server
    cmds:
      - npm run dev

  test:
    desc: Run test suite
    cmds:
      - pytest tests/

  deploy:
    desc: Deploy to production
    deps: [test, build]
    cmds:
      - ./scripts/deploy.sh

No tabs to break. No special syntax to remember. Just YAML that does what you expect.

Use it as a project entrypoint

I organize projects with one Taskfile.yaml at the root and scripts in tasks/. Taskfile becomes the entrypoint, scripts do the actual work.

project/
├── Taskfile.yaml
├── tasks/
│   ├── setup.sh
│   ├── deploy.py
│   ├── backup.nu
│   └── check.sh
├── src/
└── tests/

Your Taskfile.yaml just calls the scripts:

version: '3'

tasks:
  setup:
    desc: Initialize development environment
    cmds:
      - bash tasks/setup.sh

  deploy:
    desc: Deploy application
    cmds:
      - python tasks/deploy.py {{.ENV}}

  backup:
    desc: Backup database
    cmds:
      - nu tasks/backup.nu

  check:
    desc: Run health checks
    cmds:
      - bash tasks/check.sh

Scripts can be in any language. Bash for system stuff, Python for logic, Nushell for data processing. Taskfile doesn’t care. It just runs them.

Why this structure works

Scripts get complex. Taskfile keeps them organized and discoverable.

New developers run task and see what’s available. No hunting through bash scripts or reading README files.

You can test scripts independently. Run bash tasks/setup.sh directly if you need to debug. Taskfile is just the entrypoint.

Dependencies work like you’d expect:

tasks:
  build:
    desc: Build application
    cmds:
      - bash tasks/build.sh

  test:
    desc: Run tests
    deps: [build]
    cmds:
      - bash tasks/test.sh

  deploy:
    desc: Deploy to production
    deps: [test]
    cmds:
      - python tasks/deploy.py

Run task deploy and it runs build, then test, then deploy. Clean and predictable.

Variables and templating

Pass variables to your tasks:

tasks:
  deploy:
    desc: Deploy to environment
    cmds:
      - python tasks/deploy.py --env={{.ENV}}

  logs:
    desc: Show logs for service
    cmds:
      - bash tasks/logs.sh {{.SERVICE}}

Call it with task deploy ENV=prod or task logs SERVICE=api.

You can set default values:

tasks:
  deploy:
    desc: Deploy to environment
    vars:
      ENV: staging
    cmds:
      - python tasks/deploy.py --env={{.ENV}}

Task-specific directories

Run tasks from specific directories:

tasks:
  frontend:
    desc: Start frontend dev server
    dir: frontend/
    cmds:
      - npm run dev

  backend:
    desc: Start backend server
    dir: backend/
    cmds:
      - go run main.go

The task changes to that directory before running. Cleaner than cd in scripts.

Silent tasks and output control

Some tasks are noisy. Control the output:

tasks:
  install:
    desc: Install dependencies
    cmds:
      - npm install
    silent: true

  status:
    desc: Check service status
    cmds:
      - task: check-db
      - task: check-api
    silent: true

  check-db:
    cmds:
      - bash tasks/check-db.sh

  check-api:
    cmds:
      - bash tasks/check-api.sh

When to use Makefiles instead

If you’re building C, C++, or anything that needs real dependency tracking based on file timestamps, use Make. That’s what it’s built for.

Taskfile is for running commands, not tracking source file dependencies. It doesn’t know if a file changed. It just runs tasks.

For project automation, dev workflows, and deployment scripts, Taskfile wins on DevEx.

Getting started

Install it:

# macOS
brew install go-task

# Linux
sh -c '$(curl --location https://taskfile.dev/install.sh)' -- -d -b ~/.local/bin

# Or use your package manager

Create a Taskfile.yaml:

version: '3'

tasks:
  hello:
    desc: Say hello
    cmds:
      - echo 'Hello from Taskfile'

Run task hello. That’s it.

Start moving your scripts into tasks/ and reference them from Taskfile.yaml. Your project becomes instantly more discoverable.

Bottom line

You can do the same things with Make. Makefiles work great, they’re battle-tested, installed everywhere, and if you know the syntax, they’re fast to write.

Taskfile has built-in docs and YAML syntax. That’s it. Make is still great.

Reality is often more nuanced. But me? Nuance bores me. I'd rather be clear.

Comments