Taskfile as Makefile Alternative
Taskfile is a modern task runner that uses YAML syntax to define build and automation tasks, offering a clean alternative to Makefile with better cross-platform support and readability. With features like task dependencies, variables, dotenv support, and CI/CD integration, Taskfile simplifies project automation on Linux without the pitfalls of Makefile tab-sensitive syntax.
Prerequisites
- Linux (Ubuntu/Debian or CentOS/Rocky)
- Basic shell knowledge
- A project that needs task automation
Installing Task
# Install via the official install script (Linux)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
# Verify installation
task --version
# Ubuntu/Debian via snap
sudo snap install task --classic
# Using Go (if Go is installed)
go install github.com/go-task/task/v3/cmd/task@latest
# CentOS/Rocky - download binary directly
curl -sL https://github.com/go-task/task/releases/latest/download/task_linux_amd64.tar.gz | \
tar -xz -C /usr/local/bin task
Basic Taskfile Structure
Create a Taskfile.yml in your project root:
# Taskfile.yml
version: '3'
tasks:
default:
desc: Show available tasks
cmds:
- task --list
build:
desc: Build the application
cmds:
- echo "Building..."
- go build -o bin/app ./cmd/app
test:
desc: Run all tests
cmds:
- go test ./... -v -cover
clean:
desc: Remove build artifacts
cmds:
- rm -rf bin/
- rm -f coverage.out
Run tasks:
# List all available tasks
task --list
task -l
# Run a specific task
task build
# Run default task
task
# Run multiple tasks
task clean build test
# Dry run (show commands without executing)
task --dry build
Task Dependencies and Ordering
Define task dependencies to ensure proper execution order:
version: '3'
tasks:
build:
desc: Build the application
deps: [generate, lint] # Run these first (in parallel)
cmds:
- go build -o bin/app ./cmd/app
generate:
desc: Run code generation
cmds:
- go generate ./...
lint:
desc: Run linter
cmds:
- golangci-lint run
deploy:
desc: Build and deploy
deps:
- task: test
- task: build
cmds:
- ./scripts/deploy.sh
# Sequential dependencies (not parallel)
release:
desc: Full release pipeline
cmds:
- task: clean
- task: test
- task: build
- task: push-image
Control parallelism:
tasks:
test-all:
desc: Run tests in parallel
deps:
- task: test-unit
- task: test-integration
- task: test-e2e
# Force sequential with run: once
setup:
deps:
- task: install-deps
cmds:
- task: configure-db
- task: run-migrations
Variables and Environment
version: '3'
vars:
APP_NAME: myapp
VERSION:
sh: git describe --tags --always # Dynamic variable from command
env:
GO_ENV: production
LOG_LEVEL: info
tasks:
build:
desc: Build with version info
vars:
BUILD_DATE:
sh: date -u +"%Y-%m-%dT%H:%M:%SZ"
cmds:
- |
go build \
-ldflags "-X main.Version={{.VERSION}} -X main.BuildDate={{.BUILD_DATE}}" \
-o bin/{{.APP_NAME}} ./cmd/app
tag:
desc: Create a git tag
vars:
TAG: '{{.TAG | default "v0.0.1"}}'
cmds:
- git tag -a {{.TAG}} -m "Release {{.TAG}}"
- git push origin {{.TAG}}
Using .env Files
version: '3'
dotenv: ['.env', '.env.local']
tasks:
start:
desc: Start with environment variables
cmds:
- echo "Using DB: $DATABASE_URL"
- ./bin/app serve
Pass variables at runtime:
# Override a variable
task build VERSION=v1.2.3
# Set environment variable
task start LOG_LEVEL=debug
# Multiple variables
task deploy APP_NAME=myapp VERSION=v1.0.0 ENV=prod
Conditionals and Guards
Use preconditions to validate before running:
version: '3'
tasks:
deploy:
desc: Deploy to production
preconditions:
- sh: test -f bin/app
msg: "Binary not found. Run 'task build' first"
- sh: git diff --quiet
msg: "Uncommitted changes detected. Commit before deploying"
- sh: '[ "{{.ENV}}" = "production" ]'
msg: "ENV must be set to 'production'"
cmds:
- ./scripts/deploy.sh
# Run only if file doesn't exist (caching)
generate-proto:
desc: Generate protobuf files
sources:
- proto/**/*.proto
generates:
- gen/**/*.go
cmds:
- protoc --go_out=gen ./proto/*.proto
Use status to skip tasks when already done:
tasks:
install-tools:
desc: Install required tools
status:
- which golangci-lint
- which protoc
cmds:
- go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- apt-get install -y protobuf-compiler
Including External Taskfiles
Split large Taskfiles into modular includes:
# Taskfile.yml
version: '3'
includes:
docker:
taskfile: ./taskfiles/docker.yml
dir: .
db:
taskfile: ./taskfiles/db.yml
deploy:
taskfile: ./taskfiles/deploy.yml
optional: true # Don't fail if file doesn't exist
tasks:
setup:
desc: Full project setup
cmds:
- task: docker:build
- task: db:migrate
# taskfiles/docker.yml
version: '3'
vars:
IMAGE: myapp
tasks:
build:
desc: Build Docker image
cmds:
- docker build -t {{.IMAGE}}:latest .
push:
desc: Push to registry
cmds:
- docker push {{.IMAGE}}:latest
Run namespaced tasks:
# Run docker:build task
task docker:build
# Run db:migrate task
task db:migrate
CI/CD Integration
GitHub Actions
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
- name: Run CI pipeline
run: task ci
# Taskfile.yml ci task:
# ci:
# cmds:
# - task: lint
# - task: test
# - task: build
GitLab CI
# .gitlab-ci.yml
before_script:
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
test:
script:
- task test
build:
script:
- task build
Troubleshooting
Task not found:
# Check Taskfile is in current directory or specify path
task --taskfile /path/to/Taskfile.yml list
# Check YAML syntax
python3 -c "import yaml; yaml.safe_load(open('Taskfile.yml'))" && echo "Valid YAML"
Variable not expanding:
# Variables use {{.VAR}} syntax, not $VAR in task definitions
# For shell variables in cmds, use $VAR as normal
# Debug variable values
task --verbose build
Dependency loop detected:
# Task detects circular dependencies automatically
# Review your deps: sections for circular references
# Use task --dry to trace execution order
task --dry deploy
Command runs every time despite generates/sources:
# Ensure sources and generates paths are correct
# Task uses file modification times for caching
# Force run ignoring status
task --force generate-proto
Conclusion
Taskfile provides a readable, YAML-based alternative to Makefiles that handles cross-platform compatibility, parallel task execution, and variable management elegantly. Its support for includes, dotenv files, and preconditions makes it well-suited for complex projects. For teams already comfortable with YAML-based CI/CD pipelines, Taskfile integrates naturally and reduces the friction of shell-based project automation.


