Madikon 2026

Finanzen & Vorsorge · Tech, Web & Code

Kein Tracking

Madikon
Journal

Task – Der moderne Make-Ersatz für alle Plattformen

Task ist ein plattformunabhängiger Task-Runner. Warum er die bessere Alternative zu Makefiles ist – mit Einrichtung, Node- und Docker-Beispielen.

Task – Der moderne Make-Ersatz für alle Plattformen

Ein Task-Runner im Geiste von Make, aber ohne die Altlasten: YAML statt Tab-Fallen, ein einzelnes Binary statt Unix-Abhängigkeiten, und dasselbe Taskfile auf Linux, macOS und Windows.

1. Einleitung: Das Makefile-Problem

Wer jahrelang mit Makefiles gearbeitet hat, kennt ihre Stärken. Ein make build, ein make test, ein make deploy – schnell sind die Befehle ausgeführt, die sich niemand merken kann und die auch niemand von Hand eintippen möchte. Hinter den Befehlen steckt eine deklarative Beschreibung dessen, was getan werden soll, nicht wie man es manuell eintippt. Make ist schnell, überall vorhanden (zumindest auf Unix) und nach fast 50 Jahren noch immer der De-facto-Standard für Build-Prozesse.

Ich bin vor ein paar Jahren arbeitsbedingt von Mac auf Windows gewechselt. 😒 Make installieren, dachte ich, das dauert zehn Minuten. Zwei Stunden und diverse Anläufe später lief es – nur wusste ich ehrlich gesagt nicht mehr, welche der Versuche und Befehle mich zum Ziel gebracht haben. Die eigentliche Erkenntnis war eine andere: Wenn ich hier schon kämpfe, will ich das meinen Kollegen, die ebenfalls auf Windows arbeiten, nicht antun.

Die Installation war nur der erste Teil des Problems. Denn selbst wenn Make einmal läuft, warten unter Windows drei Stolperfallen. Windows bringt keinen Make-Interpreter mit – man muss MinGW, Cygwin oder ein separates GnuWin32-Paket installieren, nur um ein Skript auszuführen. Und selbst dann wäre das Makefile vermutlich kaputt, denn es verlässt sich stillschweigend auf sh, rm, cp, mkdir -p, grep und Co. Unter cmd.exe oder PowerShell funktioniert dieselbe Datei schlicht nicht.

Dazu kommt die berüchtigte Tab-Falle. Ein Makefile mischt Tabs und Spaces, und ein einziges falsch eingerücktes Leerzeichen erzeugt die Fehlermeldung „missing separator". Mit einem modernen Editor, der Spaces einsetzt, ist das ein ständiger Stolperstein. Und wer die Falle einmal verstanden hat, steht vor dem nächsten Problem: Variablen mit $(VAR) oder ${VAR}, automatische Variablen wie $@, implizite Regeln, PHONY-Deklarationen. Die Lernkurve ist steil, das Ergebnis schwer wartbar.

Die Folge: Genau die Automatisierung, die einem auf Linux oder Mac den Alltag erleichtert, fällt unter Windows weg. Man tippt Befehle wieder von Hand. Oder man pflegt zwei parallele Skriptsysteme, eines für Unix, eines für Windows.

Task löst genau dieses Problem. Der Task-Runner übernimmt das bewährte Make-Prinzip und wirft die Altlasten über Bord.

2. Was ist Task?

Task ist ein Task-Runner und Build-Werkzeug im Stil von Make. Der entscheidende Unterschied: Statt einer eigenen, fehleranfälligen Skriptsprache nutzt Task YAML als Beschreibungsformat.

Die wichtigsten Eigenschaften im Überblick:

Eigenschaft Beschreibung
YAML-basiert Lesbare, klare Struktur statt Make-Syntax. Keine Tab/Whitespace-Probleme.
Single Binary Ein einziges ausführbares Programm, keine Abhängigkeiten, keine Laufzeit-Installation.
Cross-Platform Dasselbe Taskfile.yml läuft identisch auf Linux, macOS und Windows.
Modularität Umfangreiche Projekte lassen sich in mehrere kleinere Taskfiles aufteilen und einbinden.
Make-inspiriert Konzepte wie Dependencies, Variablen und Caching sind vertraut.
Erweiterte Features Zusätzlich zu Make: --watch, parallele Ausführung, Preconditions, dynamische Variablen und mehr.

Ein erster Blick auf eine minimale Task-Datei zeigt, worum es geht:

version: '3'

tasks:
  build:
    cmds:
      - npm run build

Verglichen mit einem äquivalenten Makefile:

.PHONY: build
build:
    npm run build

Beide tun dasselbe. Aber die YAML-Variante ist selbsterklärender, hat keine versteckten Regeln und funktioniert überall, ohne dass man sich Gedanken darüber machen muss, ob make überhaupt installiert ist.

Task ist übrigens kein Nischenprojekt: Unter anderem Docker, Vercel, HashiCorp, Microsoft, Google Cloud, AWS, Anthropic, MongoDB und GoReleaser setzen es ein. Wer Task verwendet, befindet sich in guter Gesellschaft – inklusive meiner Make-geplagten Vergangenheit.

3. Warum Task statt Make?

3.1 Cross-Platform ohne Zusatz-Setup

Das ist für Windows-Nutzer der größte Gewinn. Task bringt sein eigenes Shell-Handling mit und abstrahiert die Plattformunterschiede:

  • Auf Linux/macOS führt Task Befehle standardmäßig mit sh aus.
  • Auf Windows wählt Task automatisch eine passende Shell (cmd oder PowerShell) – oder du legst selbst fest, welche Shell verwendet werden soll (mehr dazu in Abschnitt 6.5).

Damit entfällt die klassische Frage „Läuft mein Makefile unter Windows?". Ein Taskfile.yml läuft überall. Vorausgesetzt, die aufgerufenen Programme selbst sind installiert – ein npm oder docker muss natürlich auch unter Windows vorhanden sein.

3.2 Keine Tab-/Whitespace-Fallen

YAML ist da eigenwillig, aber ehrlich: Einrückung ist auch hier wichtig, nur eben konsistent (Leerzeichen, üblicherweise zwei pro Ebene) und von jedem modernen Editor korrekt unterstützt. Die berüchtigte Make-Fehlermeldung „missing separator" existiert bei Task nicht.

3.3 Eingebaute, moderne Features

Task geht über Make hinaus, ohne dass man Zusatzwerkzeuge oder Make-Hacks benötigt: Dependencies (deps) lassen einen Task automatisch zuerst andere Tasks ausführen. Variablen (vars) und Umgebungsvariablen (env) kennt man aus Make, aber lesbarer. Caching über sources/generates baut nur neu, wenn sich Quelldateien geändert haben. Mit --watch überwacht Task Dateien und startet Tasks bei Änderungen erneut. Preconditions führen Tasks nur aus, wenn bestimmte Bedingungen erfüllt sind, und parallele Ausführung lässt mehrere Tasks gleichzeitig laufen. Und silent: unterdrückt die Echo-Ausgabe der Befehle, wie man sie von Make kennt, standardmäßig.

3.4 Lesbarkeit & Wartbarkeit

YAML ist für die meisten Entwickler:innen intuitiv lesbar, gerade auch für die, die nicht täglich mit Make arbeiten. Das senkt die Einstiegshürde fürs Team erheblich: Jeder kann eine Taskfile.yml lesen und verstehen, auch ohne Make-Erfahrung.

4. Wofür man Task verwenden kann

Task ist bewusst generisch. Es ist weder an eine Programmiersprache noch an einen bestimmten Workflow gebunden. Typische Einsatzgebiete:

Build & Compile

Der klassische Make-Anwendungsfall. Installieren, bündeln, transpilieren, Artefakte erzeugen – mit Caching, sodass nur tatsächlich veränderte Teile neu gebaut werden.

Code-Generierung & Scaffolding

Viele Projekte erzeugen Teile ihres Codes automatisch: Datenbank-Schemata, die in Typdefinitionen übersetzt werden, API-Beschreibungen, aus denen Clients generiert werden, oder Konfigurationsdateien, die aus Templates entstehen. Die Werkzeuge dafür sind verschieden, das Muster ist immer gleich: Eine Quelldatei ändert sich, ein Generator muss laufen, die Ergebnisse müssen an die richtige Stelle. Mit Task legst du diesen Ablauf einmal als Task an und startest ihn mit einem Befehl, statt die Generator-Befehle jedes Mal aus der Doku zu kramen.

Linter, Formatter & Qualitäts-Pipeline

Eine Tooling-Kette (format, lint, test, check) als Folge abhängiger Tasks bündeln – der Lesbarkeit wegen und damit der ganze Vorgang mit einem einzigen Befehl läuft.

Dev-Server & lokale Entwicklung

Server starten, Hot-Reload per --watch oder lokale Umgebungen mit docker compose steuern.

Docker, Deployment & CI

Docker-Images bauen, Container starten, Releases erstellen. Da das Taskfile identisch auf lokaler Maschine und in der CI läuft, vermeidest du die klassische Diskrepanz „lokal geht's, in der CI nicht".

Kurz gesagt

Überall dort, wo du wiederkehrende Befehlsfolgen bisher in make, in Shell-Skripten oder in package.json-Scripts ("scripts") abgelegt hast, ist Task eine saubere, sprach- und plattformunabhängige Alternative.

5. Installation

Task ist ein einzelnes Binary. Es gibt mehrere Wege – hier die gängigsten für Windows, Mac und Linux:

Windows: über Scoop (empfohlen für Entwickler)

scoop install task

Windows: über Chocolatey

choco install go-task

Windows: über den Windows Package Manager (winget)

winget install Task.Task

macOS: über Homebrew (empfohlen)

brew install go-task

Linux: über Apt (Ubuntu/Debian/Mint)

curl -1sLf '@url:`https://dl.cloudsmith.io/public/task/task/setup.deb.sh`' | sudo -E bash
sudo apt install task

siehe auch Dokumentation

Installation prüfen

task --version

Zeigt die installierte Version an (aktuell 3.x).

Hinweis: Task funktioniert unabhängig von deiner Shell. Ob du Git Bash, PowerShell oder cmd nutzt – das task-Binary ist dasselbe. Die Wahl der Shell beeinflusst nur, wie die Befehle innerhalb der Tasks ausgeführt werden (siehe Abschnitt 6.5).

6. Einrichtung mit Beispielen

Dieser Abschnitt baut schrittweise eine realistische Taskfile.yml auf. Die Beispiele nutzen Node.js/npm für Build- und Tooling-Aufgaben sowie Docker für Container-Workflows – beides lässt sich aber 1:1 auf beliebige andere Sprachen und Tools übertragen.

6.1 Der minimale Einstieg

Erstelle im Projektstamm eine Datei namens Taskfile.yml:

version: '3'

tasks:
  hello:
    cmds:
      - echo "Hallo Task!"

Ausführen:

task hello

Task sucht im aktuellen Verzeichnis (und darüber) nach Taskfile.yml und führt den Task hello aus. Ohne Argument führt task den Task namens default aus – im großen Beispiel in 6.8 macht dieser per deps: [build] nichts anderes, als den Build anzustoßen.

6.2 Mehrere Befehle pro Task

version: '3'

tasks:
  build:
    desc: Baut die Anwendung
    cmds:
      - echo "Installiere Abhängigkeiten ..."
      - npm ci
      - echo "Baue das Projekt ..."
      - npm run build
      - echo "Fertig."

desc liefert eine Beschreibung, die task --list anzeigt – nützlich für die Übersicht im Team.

6.3 Dependencies

Ein Task kann andere Tasks voraussetzen. Sie laufen zuerst, in der angegebenen Reihenfolge:

version: '3'

tasks:
  build:
    deps: [generate, format]
    cmds:
      - npm run build

  generate:
    desc: Code generieren
    cmds:
      - npm run generate

  format:
    desc: Code formatieren
    cmds:
      - npx prettier --write .

Ein task build führt nun automatisch zuerst generate, dann format, dann den eigentlichen Build aus.

6.4 Variablen und Umgebungsvariablen

version: '3'

vars:
  IMAGE: "myapp"
  TAG: "1.2.3"

tasks:
  build:
    desc: "Build frontend application for production"
    env:
      NODE_ENV: production
    cmds:
      - npm ci
      - npm run build

  docker:build:
    desc: "Build Docker image using defined variables"
    deps: [build]
    cmds:
      - docker build -t {{.IMAGE}}:{{.TAG}} .
  • vars definiert Variablen (global oder pro Task), die per Go-Template-Syntax {{.NAME}} referenziert werden.
  • env setzt Umgebungsvariablen, die an die aufgerufenen Programme weitergegeben werden.

6.5 Windows & Git Bash: Shell-Auswahl

Das ist der Punkt, den ich mir beim zweiten Anlauf gewünscht hätte. Task führt Befehle standardmäßig in einer plattformspezifischen Shell aus. Mit dem sh:- bzw. cmd:-Präfix oder dem platforms:-Feld steuerst du das Verhalten explizit.

Ein Task, der unter Git Bash (sh) laufen soll:

version: '3'

tasks:
  clean:
    cmds:
      - sh: rm -rf dist/
      - sh: find . -name '*.tmp' -delete

Ein Task, der plattformabhängig unterschiedliche Befehle nutzt:

version: '3'

tasks:
  show-date:
    cmds:
      - cmd: echo %DATE% %TIME%
        platforms: [windows]
      - sh: date
        platforms: [linux, darwin]

So definierst du einen Task, der unter Windows cmd-Befehle nutzt und unter Unix sh. Genau das löst das Ausgangsproblem: Statt zweier getrennter Makefiles pflegst du eine Datei mit klar deklarierten Plattform-Zweigen.

Tipp für Git-Bash-Nutzer:innen: Wenn du Task konsequent aus der Git Bash heraus aufrufst und deine Befehle Unix-Syntax (rm, cp, find) verwenden sollen, setze das sh:-Präfix – so bist du unabhängig davon, welche Shell Task standardmäßig wählt.

6.6 Caching mit sources und generates

Ein Kern-Feature von Make ist „nur neu bauen, wenn nötig". Task kann das ebenfalls, aber moderner:

version: '3'

tasks:
  build:
    sources:
      - src/**/*.ts
    generates:
      - dist/index.js
    cmds:
      - npm run build
  • sources: die Eingabedateien, deren Änderung einen Rebuild auslöst.
  • generates: die erzeugten Ausgabedateien.

Läuft task build erneut und hat sich keine der sources-Dateien verändert (Zeitstempel- oder inhaltsbasiert), überspringt Task die Ausführung. Anders als bei Make ist dieses Verhalten opt-in: Ohne sources/generates wird ein Task immer ausgeführt. Kein PHONY-Gerangel mehr.

6.7 --watch für Hot-Reload

version: '3'

tasks:
  dev:
    desc: Dev-Server mit Auto-Reload
    cmds:
      - npm run dev
    sources:
      - src/**/*.ts
    watch: true
task dev

Task beobachtet die in sources angegebenen Dateien und startet den Task bei jeder Änderung neu – ideal für lokale Entwicklung.

6.8 Ein vollständiges, gemischtes Beispiel

Hier alles zusammen – Node-Build, Tooling-Pipeline, Dev-Workflow und Docker in einer realistischen Datei:

version: '3'

vars:
  IMAGE: myapp
  TAG: latest

tasks:
  default:
    desc: Standard-Task (Alias auf build)
    deps: [build]

  install:
    desc: Abhängigkeiten installieren
    cmds:
      - npm ci
    sources:
      - package.json
      - package-lock.json
    generates:
      - node_modules/.package-lock.json

  build:
    desc: Baut das Projekt
    deps: [install]
    sources:
      - src/**/*.ts
      - tsconfig.json
    generates:
      - dist/index.js
    cmds:
      - npm run build

  format:
    desc: Code formatieren
    cmds:
      - npx prettier --write "src/**/*.ts"

  check:
    desc: format + lint + test in einem Rutsch
    deps: [format, lint, test]

  lint:
    desc: Linter ausführen
    deps: [install]
    cmds:
      - npm run lint

  test:
    desc: Tests ausführen
    deps: [install]
    cmds:
      - npm test

  clean:
    desc: Artefakte entfernen
    cmds:
      - rm -rf dist node_modules

  dev:
    desc: Dev-Server starten
    deps: [install]
    cmds:
      - npm run dev

  docker-build:
    desc: Docker-Image bauen
    deps: [build]
    cmds:
      - docker build -t {{.IMAGE}}:{{.TAG}} .

  deploy:
    desc: Docker-Image bauen, pushen und auf Server bringen
    deps: [docker-build]
    cmds:
      - docker push {{.IMAGE}}:{{.TAG}}
      - ssh deploy@your-server.com "docker pull {{.IMAGE}}:{{.TAG}} && docker compose up -d"

Übersicht der wichtigsten Aufrufe:

task             # führt den default-Task aus (= build)
task check       # format + lint + test in einem Rutsch
task dev         # Dev-Server mit Auto-Reload
task --list      # alle Tasks mit Beschreibungen anzeigen

6.9 Ein reales Beispiel aus der Praxis

Zum Abschluss ein Auszug aus einem realen Projekt: Docker-Images für PHP in mehreren Versionen, aufgeteilt in Apache- und CLI-Variante. Interessant ist vor allem die Variablenbehandlung: Jeder Build akzeptiert die PHP-Version als CLI-Argument (task apache:build -- 8.4), fällt sonst auf den projektweiten Default zurück und leitet daraus den passenden Dockerfile-Namen ab.

version: '3'

vars:
  DEFAULT_PHP_VERSION: '8.4'

  PHP_APACHE_IMAGE_NAME: 'php-apache'
  PHP_APACHE_CONTEXT: 'php-apache'
  PHP_APACHE_DOCKERFILE: 'php-apache/Dockerfile'

  PHP_CLI_IMAGE_NAME: 'php-cli'
  PHP_CLI_CONTEXT: 'php-cli'
  PHP_CLI_DOCKERFILE: 'php-cli/Dockerfile'

tasks:
  default:
    desc: 'Show all available tasks'
    cmds:
      - task --list

  apache:build:
    desc: 'Build the PHP Apache Docker image. Usage: task apache:build -- 8.4'
    vars:
      PHP_VERSION: '{{.CLI_ARGS | default .DEFAULT_PHP_VERSION}}'
      PHP_MAJOR_VERSION: '{{index (splitList "." .PHP_VERSION) 0}}'
      DOCKERFILE: '{{.PHP_APACHE_DOCKERFILE}}.php{{.PHP_MAJOR_VERSION}}'
    cmds:
      - >
        docker build --build-arg PHP_VERSION={{.PHP_VERSION}} -f {{.DOCKERFILE}}
        --no-cache -t {{.PHP_APACHE_IMAGE_NAME}}:{{.PHP_VERSION}} {{.PHP_APACHE_CONTEXT}}

  cli:build:
    desc: 'Build the PHP CLI Docker image. Usage: task cli:build -- 8.4'
    vars:
      PHP_VERSION: '{{.CLI_ARGS | default .DEFAULT_PHP_VERSION}}'
      PHP_MAJOR_VERSION: '{{index (splitList "." .PHP_VERSION) 0}}'
      DOCKERFILE: '{{.PHP_CLI_DOCKERFILE}}.php{{.PHP_MAJOR_VERSION}}'
    cmds:
      - >
        docker build --build-arg PHP_VERSION={{.PHP_VERSION}} -f {{.DOCKERFILE}}
        --no-cache -t {{.PHP_CLI_IMAGE_NAME}}:{{.PHP_VERSION}} {{.PHP_CLI_CONTEXT}}

  cli:bash:
    desc: 'Open a Bash shell in the PHP CLI container. Usage: task cli:bash -- 8.4'
    vars:
      PHP_VERSION: '{{.CLI_ARGS | default .DEFAULT_PHP_VERSION}}'
    cmds:
      - docker run --rm -it {{.PHP_CLI_IMAGE_NAME}}:{{.PHP_VERSION}} bash

  build:
    desc: 'Build both PHP Apache and PHP CLI images'
    vars:
      PHP_VERSION: '{{.CLI_ARGS | default .DEFAULT_PHP_VERSION}}'
    cmds:
      - task: apache:build
        vars:
          PHP_VERSION: '{{.PHP_VERSION}}'
      - task: cli:build
        vars:
          PHP_VERSION: '{{.PHP_VERSION}}'

Drei Dinge sind an diesem Beispiel sehenswert: Das Namensschema cli:build gruppiert Tasks sauber nach Thema. Die desc-Zeilen dokumentieren gleich die Verwendung mit ---Argumenten, sodass task --list zur Bedienungsanleitung wird. Der build-Task zeigt, wie man Variablen an Untertasks weiterreicht, statt sie dort erneut aufzulösen. Das Original ist umfangreicher – neben der CLI-Variante gibt es dort denselben Task-Satz für Apache, plus Run- und Bash-Tasks – das Muster ist aber identisch.

7. Fazit

Task ist kein radikaler Bruch mit Make, sondern dessen konsequente Modernisierung. Es bewahrt das, was Make groß gemacht hat (deklarative Tasks, Dependencies, inkrementelle Builds), und ersetzt das, was heute schmerzt (Plattform-Abhängigkeit, kryptische Syntax, Tab-Fallen) durch ein klares, portables YAML-Format.

Ob Build, Code-Generierung, Linting, Dev-Server oder Docker-Workflow: Task deckt die gesamte Bandbreite ab und läuft überall, vom Entwickler-Laptop bis zur CI. Wer bisher zwei Skriptsysteme für Unix und Windows gepflegt hat, kann eines davon löschen.

Weiterführende Links:

Quellen:

Hinweis zur Erstellung: Textentwurf und Recherche entstanden zum Teil mit KI-Unterstützung, Redaktion und Fachinhalte von mir; das Beitragsbild ist KI-generiert. Fachliche Angaben wurden gegen die offizielle Task-Dokumentation geprüft.

Weitere Beiträge

Zurück