Task es un ejecutor de tareas escrito en Go que lee sus instrucciones de un archivo Taskfile.yml. Cubre el uso más habitual de Make (tener a mano los comandos de compilar, probar y desplegar un proyecto) con una sintaxis YAML legible, variables explícitas y sin las trampas de los tabuladores ni de los objetivos que coinciden con nombres de archivo. En este tutorial instalarás Task en Ubuntu 24.04 y construirás paso a paso un Taskfile para un proyecto de ejemplo.

Los ejemplos solo usan herramientas del sistema (cp, tar, rsync) para que puedas ejecutarlos sin instalar ningún lenguaje. En tu proyecto real sustituirás esos comandos por go build, npm run build, pytest o lo que corresponda.

Requisitos previos

  • Un servidor o equipo con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios sudo.
  • git instalado (algunas variables del ejemplo leen el commit actual).
  • Conocimientos básicos de YAML y de la terminal.

Paso 1: Instalar Task

El proyecto publica un paquete .deb en cada versión de GitHub. Descárgalo e instálalo con apt, que así queda registrado en el sistema y se puede desinstalar limpiamente:

cd /tmp
curl -fsSLO https://github.com/go-task/task/releases/latest/download/task_linux_amd64.deb
sudo apt install -y ./task_linux_amd64.deb

En servidores arm64 usa task_linux_arm64.deb. Si prefieres actualizaciones automáticas, Task también está disponible como snap con sudo snap install task --classic.

Comprueba la instalación:

task --version
Task version: v3.x.y

Activa el autocompletado en bash añadiendo esta línea a tu ~/.bashrc:

echo 'eval "$(task --completion bash)"' >> ~/.bashrc
source ~/.bashrc

Paso 2: Crear el proyecto de ejemplo y el primer Taskfile

Crea un proyecto mínimo con un par de archivos fuente:

mkdir -p ~/demo-task/src && cd ~/demo-task
git init -q
echo '<h1>Hola desde Task</h1>' > src/index.html
echo 'body { font-family: sans-serif; }' > src/style.css
git add . && git commit -qm "Primer commit"

Crea el Taskfile.yml en la raíz del proyecto:

nano Taskfile.yml
version: '3'

vars:
  APP: demo-web
  DIST: dist

tasks:
  default:
    desc: Mostrar las tareas disponibles
    cmds:
      - task --list

  build:
    desc: Copiar los fuentes a la carpeta de distribución
    cmds:
      - mkdir -p {{.DIST}}
      - cp -r src/. {{.DIST}}/

  clean:
    desc: Borrar los archivos generados
    cmds:
      - rm -rf {{.DIST}} *.tar.gz

Las variables se referencian con la sintaxis de plantillas de Go, {{.NOMBRE}}. Solo las tareas con desc aparecen en task --list.

Lista y ejecuta las tareas:

task --list
task build
task: Available tasks for this project:
* build:       Copiar los fuentes a la carpeta de distribución
* clean:       Borrar los archivos generados
* default:     Mostrar las tareas disponibles
task: [build] mkdir -p dist
task: [build] cp -r src/. dist/

Task muestra cada comando antes de ejecutarlo con el prefijo task: [nombre]. Si un comando falla, la tarea se detiene con el mismo código de salida, igual que Make.

Paso 3: Evitar trabajo repetido con sources y generates

Make decide si recompilar comparando fechas de archivos. Task hace lo mismo con sources y generates, pero por defecto compara sumas de verificación del contenido. Cambia la tarea build:

  build:
    desc: Copiar los fuentes a la carpeta de distribución
    sources:
      - src/**/*
    generates:
      - '{{.DIST}}/index.html'
    cmds:
      - mkdir -p {{.DIST}}
      - cp -r src/. {{.DIST}}/

Ejecuta la tarea dos veces seguidas:

task build
task build
task: [build] mkdir -p dist
task: [build] cp -r src/. dist/
task: Task "build" is up to date

La segunda vez no hace nada porque los fuentes no han cambiado. Modifica src/index.html y vuelve a ejecutarla para ver que se repite. Para forzarla aunque esté al día, usa task --force build. Task guarda las sumas en el directorio .task/; añádelo a tu .gitignore.

Paso 4: Encadenar tareas con dependencias

Una tarea puede depender de otras. Las indicadas en deps se ejecutan antes y en paralelo; si necesitas un orden estricto, llámalas desde cmds con task:. Añade una tarea que empaquete el resultado:

  package:
    desc: Crear un tar.gz con la versión actual
    deps: [build]
    vars:
      COMMIT:
        sh: git rev-parse --short HEAD
    cmds:
      - tar -czf {{.APP}}-{{.COMMIT}}.tar.gz -C {{.DIST}} .
      - ls -lh {{.APP}}-{{.COMMIT}}.tar.gz

La variable COMMIT es dinámica: su valor es la salida del comando indicado en sh. Ejecuta el empaquetado:

task package
task: Task "build" is up to date
task: [package] tar -czf demo-web-a1b2c3d.tar.gz -C dist .
task: [package] ls -lh demo-web-a1b2c3d.tar.gz
-rw-rw-r-- 1 usuario usuario 312 sep 25 10:00 demo-web-a1b2c3d.tar.gz

Para ver qué haría una tarea sin ejecutarla, usa task --dry package.

Paso 5: Variables desde la línea de comandos y desde .env

Las variables se pueden pasar al invocar Task, y tienen prioridad sobre las del Taskfile. Además, la clave dotenv carga archivos .env como variables de entorno.

Crea un archivo .env con el destino del despliegue:

echo 'DEPLOY_PATH=/tmp/demo-deploy' > .env

Añade dotenv al principio del Taskfile, justo después de version:

version: '3'

dotenv: ['.env']

Y una tarea de despliegue que exige una variable ENV:

  deploy:
    desc: Publicar dist en DEPLOY_PATH (uso, task deploy ENV=staging)
    deps: [build]
    requires:
      vars: [ENV]
    preconditions:
      - sh: git diff --quiet
        msg: Hay cambios sin confirmar en git. Haz commit antes de desplegar.
    cmds:
      - mkdir -p "$DEPLOY_PATH/{{.ENV}}"
      - rsync -a --delete {{.DIST}}/ "$DEPLOY_PATH/{{.ENV}}/"
      - echo "Desplegado {{.APP}} en $DEPLOY_PATH/{{.ENV}}"
  • requires.vars detiene la tarea si falta la variable, con un mensaje claro.
  • preconditions ejecuta comprobaciones previas; si alguna devuelve un código distinto de 0, muestra msg y aborta.
  • $DEPLOY_PATH es una variable de entorno que viene del .env, por eso se usa con la sintaxis del shell.

Prueba primero sin la variable y después con ella:

task deploy
task deploy ENV=staging
task: Task "deploy" cancelled because it is missing required variables: ENV
task: Task "build" is up to date
task: [deploy] mkdir -p "$DEPLOY_PATH/staging"
task: [deploy] rsync -a --delete dist/ "$DEPLOY_PATH/staging/"
task: [deploy] echo "Desplegado demo-web en $DEPLOY_PATH/staging"
Desplegado demo-web en /tmp/demo-deploy/staging

Si tienes cambios sin confirmar, la precondición lo impedirá. Añade .task/, dist/, *.tar.gz y .env a .gitignore y haz commit para que el árbol quede limpio.

Paso 6: Ejecutar una tarea solo cuando haga falta con status

status es lo contrario de preconditions: si todos sus comandos terminan con éxito, la tarea se considera hecha y se omite. Es útil para pasos de preparación idempotentes:

  setup:
    desc: Crear el .env a partir de la plantilla si no existe
    status:
      - test -f .env
    cmds:
      - cp .env.example .env

Con .env ya presente, task setup responde task: Task "setup" is up to date sin tocar nada.

Paso 7: Dividir el Taskfile con includes

En proyectos grandes conviene separar las tareas por área. Crea un Taskfile para las tareas de servidor:

mkdir -p taskfiles
nano taskfiles/servidor.yml
version: '3'

tasks:
  espacio:
    desc: Mostrar el espacio libre en disco
    cmds:
      - df -h /

  servicios:
    desc: Listar los servicios de systemd que han fallado
    cmds:
      - systemctl --failed --no-pager

Inclúyelo en el Taskfile.yml principal, al mismo nivel que vars y tasks:

includes:
  srv:
    taskfile: ./taskfiles/servidor.yml
    optional: true

Las tareas incluidas se invocan con el espacio de nombres como prefijo:

task srv:espacio
task: [srv:espacio] df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        39G  4.2G   35G  11% /

Con optional: true, Task no falla si el archivo incluido no existe.

Paso 8: Usar Task en GitHub Actions

Como el Taskfile es la única fuente de verdad de cómo se compila el proyecto, el pipeline de CI solo tiene que instalar Task y llamar a las mismas tareas que usas en local. Crea .github/workflows/ci.yml:

name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Instalar Task
        uses: arduino/setup-task@v2
        with:
          version: 3.x
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Empaquetar
        run: task package

En otros sistemas de CI instala el .deb del paso 1 en la imagen del runner o usa una imagen que ya incluya task.

Taskfile frente a Makefile

AspectoMakeTask
FormatoSintaxis propia, recetas con tabulador obligatorioYAML
Recompilar solo si hay cambiosPor fecha de archivosPor suma de verificación (o fecha, con method: timestamp)
VariablesExpansión en varias fases, difícil de depurarPlantillas Go, dinámicas con sh:
Archivos .envNodotenv
Dependencias en paraleloCon make -jPor defecto en deps
DisponibilidadPreinstalado en casi todos los sistemasHay que instalar un binario

Si tu proyecto compila C o C++ y depende del grafo de archivos de Make, sigue con Make. Si lo usas solo como lista de comandos del proyecto, Task es más fácil de leer y mantener.

Solución de problemas

task: No Taskfile found. Task busca Taskfile.yml (o Taskfile.yaml, taskfile.yml) en el directorio actual y en los superiores. Comprueba el nombre del archivo o indica la ruta con task --taskfile ruta/Taskfile.yml.

Una variable aparece vacía. Las variables del Taskfile y de la línea de comandos se usan con {{.VAR}}; las de entorno y las del .env, con $VAR dentro del comando. Revisa el comando final con task --dry nombre.

La tarea dice is up to date pero quieres ejecutarla. Usa --force, o borra el directorio .task/ para reiniciar todas las sumas de verificación.

Error de YAML al cargar el Taskfile. Los valores que contienen {{ }} al principio o dos puntos deben ir entre comillas, por ejemplo - '{{.DIST}}/index.html'.

Conclusión

Tienes Task instalado y un Taskfile que compila solo cuando cambian los fuentes, empaqueta con el commit actual, despliega con variables obligatorias y precondiciones, se divide en archivos incluidos y se reutiliza tal cual en CI. Como siguientes pasos, traslada los comandos de tu Makefile actual tarea por tarea, usa task --summary nombre para documentar las tareas más complejas y añade task --list al README del proyecto.