Taskfile como Alternativa a Makefile

Taskfile es una herramienta de automatización moderna escrita en Go que usa YAML en lugar de la sintaxis críptica de Makefile. Permite definir tareas, dependencias y variables de forma clara y portable, siendo una excelente alternativa a Make para proyectos que necesitan automatización de comandos sin la complejidad de herramientas CI/CD completas. Esta guía cubre todo lo necesario para usar Taskfile en proyectos Linux de producción.

Requisitos Previos

  • Linux (Ubuntu/Debian o CentOS/Rocky)
  • Acceso a terminal con permisos de usuario (sudo para instalación del sistema)
  • Conocimientos básicos de YAML

Instalación de Task

# Método 1: Script oficial de instalación (recomendado)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

# Método 2: Con Homebrew (si está disponible en Linux)
brew install go-task/tap/go-task

# Método 3: Con snap
snap install task --classic

# Método 4: Descarga manual del binario
curl -L https://github.com/go-task/task/releases/latest/download/task_linux_amd64.tar.gz | tar xz
sudo mv task /usr/local/bin/

# Verificar la instalación
task --version

Autocompletado en bash/zsh

# Para bash
task --completion bash > ~/.task_completion
echo "source ~/.task_completion" >> ~/.bashrc

# Para zsh
task --completion zsh > ~/.zsh_completions/_task
echo "fpath=(~/.zsh_completions $fpath)" >> ~/.zshrc
source ~/.zshrc

Estructura Básica de un Taskfile

El archivo Taskfile.yml debe estar en la raíz del proyecto:

# Taskfile.yml
version: '3'

# Variables globales del proyecto
vars:
  APP_NAME: mi-aplicacion
  BUILD_DIR: ./dist

tasks:
  # Tarea por defecto (se ejecuta con solo 'task')
  default:
    desc: "Mostrar tareas disponibles"
    cmds:
      - task --list

  # Tarea de construcción básica
  build:
    desc: "Compilar la aplicación"
    cmds:
      - echo "Compilando {{.APP_NAME}}..."
      - go build -o {{.BUILD_DIR}}/{{.APP_NAME}} ./cmd/main.go

  # Tarea de pruebas
  test:
    desc: "Ejecutar pruebas unitarias"
    cmds:
      - go test ./... -v -cover

  # Tarea de limpieza
  clean:
    desc: "Eliminar archivos generados"
    cmds:
      - rm -rf {{.BUILD_DIR}}
      - echo "Limpieza completada"
# Listar todas las tareas disponibles
task --list

# Ejecutar una tarea específica
task build

# Ejecutar múltiples tareas
task clean build test

# Ver qué comandos ejecutaría una tarea sin ejecutarlos
task --dry build

Dependencias y Orden de Ejecución

version: '3'

tasks:
  # Tarea que depende de otras tareas (se ejecutan antes)
  deploy:
    desc: "Desplegar la aplicación"
    deps: [lint, test, build]
    cmds:
      - echo "Desplegando en producción..."
      - rsync -avz dist/ usuario@servidor:/var/www/app/

  lint:
    desc: "Verificar calidad del código"
    cmds:
      - golangci-lint run ./...

  test:
    desc: "Ejecutar todas las pruebas"
    cmds:
      - go test ./... -race

  build:
    desc: "Compilar binario"
    cmds:
      - go build -o dist/app ./cmd/

  # Dependencias en paralelo (más rápido)
  ci:
    desc: "Pipeline completo de CI"
    deps:
      - task: lint
      - task: test
    cmds:
      - task: build
      - task: deploy

Control de ejecución con fuentes y destinos

tasks:
  build:
    desc: "Compilar solo si los fuentes cambiaron"
    sources:
      - "**/*.go"
      - go.mod
      - go.sum
    generates:
      - dist/app
    cmds:
      - go build -o dist/app ./cmd/

Variables y Entorno

version: '3'

# Variables con diferentes niveles de precedencia
vars:
  # Variable estática
  VERSION: "1.0.0"
  
  # Variable calculada con comando shell
  GIT_COMMIT:
    sh: git rev-parse --short HEAD
  
  # Variable con valor por defecto (sobreescribible desde CLI)
  ENV: "development"

# Cargar variables desde archivo .env
dotenv: ['.env', '.env.local']

tasks:
  info:
    desc: "Mostrar información del proyecto"
    cmds:
      - echo "Versión: {{.VERSION}}"
      - echo "Commit: {{.GIT_COMMIT}}"
      - echo "Entorno: {{.ENV}}"
      # Variables de entorno del sistema también disponibles
      - echo "Usuario: $USER"

  build:
    desc: "Construir con metadatos de versión"
    vars:
      # Variable local a esta tarea
      BUILD_TIME:
        sh: date -u +"%Y-%m-%dT%H:%M:%SZ"
    cmds:
      - |
        go build \
          -ldflags "-X main.version={{.VERSION}} -X main.commit={{.GIT_COMMIT}} -X main.buildTime={{.BUILD_TIME}}" \
          -o dist/app ./cmd/

  # Pasar variables desde la línea de comandos
  # Uso: task deploy ENV=produccion VERSION=2.0.0
  deploy:
    desc: "Desplegar en entorno específico"
    cmds:
      - echo "Desplegando versión {{.VERSION}} en {{.ENV}}"
      - ./scripts/deploy.sh {{.ENV}}

Condicionales y Validaciones

version: '3'

tasks:
  deploy:
    desc: "Desplegar con verificaciones previas"
    # Verificar que se cumplan condiciones antes de ejecutar
    preconditions:
      - sh: "git diff --quiet"
        msg: "Hay cambios sin commitear. Haz commit antes de desplegar."
      - sh: "test -f dist/app"
        msg: "El binario no existe. Ejecuta 'task build' primero."
    cmds:
      - echo "Todas las verificaciones pasaron, desplegando..."

  # Ejecutar solo si no existe el archivo de destino
  generate-config:
    desc: "Generar configuración inicial"
    status:
      - test -f config/app.yaml
    cmds:
      - cp config/app.yaml.example config/app.yaml
      - echo "Configuración generada"

  # Tarea de plataforma específica
  install-deps:
    desc: "Instalar dependencias del sistema"
    cmds:
      # Detectar el sistema operativo
      - |
        if command -v apt-get &>/dev/null; then
          sudo apt-get install -y curl git jq
        elif command -v dnf &>/dev/null; then
          sudo dnf install -y curl git jq
        else
          echo "Sistema operativo no soportado"
          exit 1
        fi

Includes y Taskfiles Modulares

Para proyectos grandes, divide las tareas en archivos separados:

# Taskfile.yml principal
version: '3'

includes:
  # Incluir Taskfile de Docker
  docker:
    taskfile: ./taskfiles/docker.yml
    dir: .  # Directorio desde donde se ejecutan los comandos
  
  # Incluir Taskfile de base de datos
  db:
    taskfile: ./taskfiles/database.yml
    optional: true  # No fallar si no existe
  
  # Incluir con alias
  k8s:
    taskfile: ./taskfiles/kubernetes.yml
    aliases: [kubernetes, kube]

tasks:
  default:
    cmds:
      - task --list
# taskfiles/docker.yml
version: '3'

tasks:
  build:
    desc: "Construir imagen Docker"
    cmds:
      - docker build -t {{.IMAGE_NAME}}:{{.TAG}} .

  push:
    desc: "Subir imagen al registro"
    cmds:
      - docker push {{.IMAGE_NAME}}:{{.TAG}}

  up:
    desc: "Levantar servicios con Docker Compose"
    cmds:
      - docker compose up -d

  down:
    desc: "Detener servicios"
    cmds:
      - docker compose down
# Ejecutar tareas de módulos incluidos
task docker:build
task docker:push
task db:migrate

Integración con CI/CD

GitHub Actions

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Instalar Task
        uses: arduino/setup-task@v2
        with:
          version: 3.x
      
      - name: Ejecutar CI completo
        run: task ci

GitLab CI

# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  TASK_VERSION: "3.x"

before_script:
  - sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

test:
  stage: test
  script:
    - task test

build:
  stage: build
  script:
    - task build
  artifacts:
    paths:
      - dist/

deploy:
  stage: deploy
  script:
    - task deploy ENV=produccion
  only:
    - main

Taskfile completo para proyecto web

# Taskfile.yml de ejemplo completo
version: '3'

dotenv: ['.env']

vars:
  APP: mi-app
  REGISTRY: registry.empresa.com
  TAG:
    sh: git describe --tags --always --dirty 2>/dev/null || echo "dev"

tasks:
  # Desarrollo local
  dev:
    desc: "Iniciar servidor de desarrollo"
    cmds:
      - docker compose -f docker-compose.dev.yml up

  # Calidad de código
  lint:
    desc: "Ejecutar linters"
    cmds:
      - eslint src/
      - prettier --check "src/**/*.{js,ts}"

  # Pruebas
  test:
    desc: "Ejecutar suite completa de pruebas"
    cmds:
      - jest --coverage --ci

  # Construcción
  build:
    desc: "Construir para producción"
    deps: [lint, test]
    cmds:
      - npm run build
      - docker build -t {{.REGISTRY}}/{{.APP}}:{{.TAG}} .

  # Despliegue
  deploy:
    desc: "Desplegar en producción"
    preconditions:
      - sh: "[ '{{.ENV}}' != '' ]"
        msg: "Define ENV=staging o ENV=produccion"
    cmds:
      - docker push {{.REGISTRY}}/{{.APP}}:{{.TAG}}
      - kubectl set image deployment/{{.APP}} app={{.REGISTRY}}/{{.APP}}:{{.TAG}}
      - kubectl rollout status deployment/{{.APP}}

Solución de Problemas

Task no encontrado: command not found

# Verificar que el binario está en el PATH
which task
echo $PATH

# Reinstalar en ubicación estándar
sudo sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

Variables no se expanden correctamente

# Las variables de Taskfile usan {{.VAR}}, no $VAR
# $VAR se refiere a variables de entorno del sistema
# Verificar con --dry
task --dry mi-tarea

Tarea se ejecuta aunque no haya cambios

# Verificar que sources y generates están bien configurados
task --verbose mi-tarea

# Forzar re-ejecución ignorando cache
task --force mi-tarea

Error de precondición poco descriptivo

# Usar msg descriptivo en preconditions
# Y ejecutar con --verbose para más información
task --verbose deploy

Conclusión

Taskfile resuelve los problemas más comunes de Makefile: sintaxis intuitiva en YAML, soporte nativo de variables, dependencias declarativas y portabilidad entre sistemas operativos. Es especialmente útil para proyectos modernos que necesitan automatizar builds, pruebas y despliegues sin introducir la complejidad de una plataforma CI/CD completa. La modularidad con includes lo hace escalable para proyectos monorepo grandes.