# Castor runs your build tasks as PHP functions with attributes

> Castor is a task runner for PHP projects where tasks are ordinary functions marked with an attribute in a castor.php file, with built-in helpers for running processes, console output, file watching and the filesystem. It is MIT licensed and built on Symfony Console, and it exists to replace Makefiles, shell scripts and YAML with code your IDE can autocomplete.

**jolicode/castor** — 🦫 A lightweight and modern task runner for Automation, CI/CD & DevOps.

- Repository: https://github.com/jolicode/castor
- Website: https://castor.jolicode.com
- Stars: 557 · Forks: 32
- Language: PHP
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/jolicode-castor

## A task is a function with an attribute

Castor is a task runner for automation, CI/CD and DevOps that puts the tasks in PHP. The pitch is that you write your automation scripts in PHP and run them from the CLI, with no need for Bash, Makefiles or YAML.

The mechanism is an attribute. Tasks are set up as typical PHP functions marked with the AsTask attribute in a castor.php file:

```php
use Castor\Attribute\AsTask;

#[AsTask()]
function hello(): void
{
    echo 'Hello from castor';
}
```

That file exposes a hello task you run with castor hello, and the console output is:

```console
$ castor hello
Hello from castor
```

The project describes itself as 100% PHP, fast and native with no configuration and no boilerplate, and the practical meaning of that is that a task is a function in your editor, so it gets autocompletion, it gets refactoring, and it can be unit tested like any other function. A Makefile target cannot do that.

The documentation also lists the built-in helper functions that come pre-packaged, with run() for external processes, io() for console output, watch() for reacting to file changes and fs() for filesystem work, plus a reference of more advanced functions.

## Why the project exists instead of Make, Robo or Phing

The project states its position against the alternatives directly, in a section headed why not Make, Robo, Phing, Deployer or Symfony Console.

The reasoning has three parts. Make is not PHP, and is hard to maintain in large projects. The others are described as either too verbose, object-oriented heavy, requiring YAML or XML configuration, or specialised in deployment only. And Symfony Console is called a great base, with the note that Castor is built on top of it and gives you superpowers.

That last point is the honest framing of what Castor is. It is a Symfony Console application with a task-attribute layer and a set of helpers on top, which means the argument parsing, the output formatting and the exit code handling are Symfony's, and Castor adds the developer experience layer.

The comparison with a Makefile is the sharpest one to hold on to. A Makefile has targets, dependencies and a shell as its expression language. Castor has functions, typed parameters and PHP as its expression language, with the shell still available when you want it, because run() executes external commands and the destroy task in the documentation runs docker-compose directly.

There is a dedicated FAQ page with detailed comparisons, and a community examples page with real-world examples.

## Arguments, options and context, handled by the framework

The features list leads with parsing of arguments and options, which is described as simplifying input handling, and that is the part you would otherwise write by hand in every task.

The destroy task in the documentation shows the pattern. The function takes a typed bool argument, and the attribute carries a description string:

```php
#[AsTask(description: 'Clean the infrastructure (remove container, volume, networks)')]
function destroy(bool $force = false)
{
    if (!$force) {
        io()->warning('This will permanently remove all containers, volumes, networks... created for this project.');
        io()->comment('You can use the --force option to avoid this confirmation.');

        if (!io()->confirm('Are you sure?', false)) {
            io()->comment('Aborted.');

            return;
        }
    }

    run('docker-compose down -v --remove-orphans --volumes --rmi=local');

    notify('The infrastructure has been destroyed.')
}
```

The bool parameter becomes a flag, the default of false means the task asks before doing the destructive thing, and the io() helper gives warning, comment and confirm output. The task then shells out with run() and finishes with a notification.

The documentation has separate pages for task arguments, for using the context, and for remote execution, which is the case of running a task on a machine other than your own. The context object is the piece that gives a task access to the working directory and the project root, so tasks do not have to hard-code paths.

## Four helpers that cover most of what a task needs

The built-in functions are the substance of the library, and there are four that cover most automation work.

run() executes external processes, and it is described as enabling integration with external tools. This is the escape hatch that keeps Castor from being a closed world, since a task that needs to call a binary, or a Docker command as in the destroy example, calls it through run().

io() handles console output and interacts with the terminal. The destroy task uses it three ways: warning for the destructive notice, comment for explanatory text, and confirm for the interactive yes-or-no question with a default value of false. The notify() call in the same task is how a task reports a completed action to the developer.

watch() watches files and automatically triggers actions on file modifications, which is the live-reload equivalent of a build tool, and it is what a front end project needs to rebuild assets on save without a separate watcher process.

fs() creates, removes and manipulates files and directories.

The documentation points to a reference page for the more advanced functions beyond these four, and the value of the set is that they are written for the terminal rather than being a thin layer over the shell.

## Installation, and the ten second promise

The headline install is two lines:

```bash
curl "https://castor.jolicode.com/install" | bash

castor
```

The second command runs Castor with no arguments, which is the way to see what tasks are available in a project.

The documentation is clear that this is one of many routes. Castor can also be installed as a phar, as static binaries, through Composer, or as a GitHub Action, and the installation documentation covers all of them. Given that the project is a PHP tool, Composer is the route a PHP team is most likely to want, since it pins the version in the same lock file as the rest of the project.

The repository reflects a real PHP project layout rather than a small library. There is a composer.json and composer.lock for the tool itself, a separate castor.composer.json and castor.composer.lock, a castor.php at the root which is the project's own task file, an installer directory, a src directory, a tests directory, an examples directory with advanced and basic examples, a doc directory, a bin directory and a tools directory.

The quality tooling is present as well: a phpunit.xml for the test suite, phpstan.neon with a baseline, a .php-cs-fixer.php config, a CODE_OF_CONDUCT.md, a CONTRIBUTING guide, a CHANGELOG and a VERSIONING.md that documents how versions are handled.

## The use cases the project names, and who they suit

The real-world use cases listed are: run database migrations, deploy your app with one command, manage assets or translations, bootstrap environments, and automate internal tools.

Read together, they describe a team that already has a PHP application with a deploy step and a migrations step, and finds those steps expressed in a different language from the rest of the codebase. Running migrations as a PHP function means the migration code and the code that triggers it can share helpers, and a deploy task can call the same internal API client the application uses.

That is the case where Castor clearly wins, and it is narrower than the task runner category suggests. A team whose build is a Makefile calling a compiler, or a shell script calling a Docker build, gains less, because the shared-language argument does not apply.

The project is a JoliCode project, and the repository footer notes that JoliCode is sponsoring it. The community examples page collects real-world examples from other people, and the topics on the repository include hacktoberfest, so the project has an active contributor community rather than a single maintainer.

The releases are frequent: v1.9.0 on 2026-10-20, v1.8.1 on 2026-09-21, and a snapshot build tagged v1.8.1-34-g7a69a91 in between. The last push was on 2026-10-20, the same day as the newest release, so the branch and the release are in step.

## Conclusion

Adopt Castor if your automation is already PHP and you want tasks that are ordinary functions with autocompletion, argument parsing and typed return values, rather than a Makefile or a shell script. Do not adopt it if your team is not a PHP shop, since the entire value is that the tasks are PHP. Before you commit, check three things: that the Symfony Console base is acceptable to you, since the project states it is built on top of it, how your CI runner will install Castor, since the one-line install pipes a remote script into bash and the documentation also lists phar, static binary, Composer and GitHub Action routes, and that your team accepts that task arguments and options are parsed by the framework rather than by you.

## FAQ

### How do I install Castor?

The quick route is curl "https://castor.jolicode.com/install" | bash, then run castor to list the tasks. The documentation also covers installing it as a phar, as a static binary, through Composer, or as a GitHub Action.

### How do I define a task in Castor?

Write a PHP function in a castor.php file and mark it with the AsTask attribute, for example #[AsTask()] above a function hello(): void. Running castor hello then executes it, and the function body can use the built-in helpers.

### What built-in helpers does Castor provide?

run() to execute external processes, io() for console output and terminal interaction including warning, comment and confirm, watch() to trigger actions on file modifications, and fs() to create, remove and manipulate files and directories, with a reference page listing more advanced functions.

### How does Castor compare to a Makefile?

The project argues that Make is not PHP and is hard to maintain in large projects, while Castor tasks are ordinary PHP functions with autocompletion and typed parameters, and that the other tools are too verbose, object oriented heavy, need YAML or XML, or are specialised in deployment only. Castor is built on top of Symfony Console.

### What licence is Castor released under?

MIT, with the LICENSE file at the top of the repository. The project is MIT licensed and actively released, with v1.9.0 tagged on 2026-10-20.

## Sources

- [jolicode/castor on GitHub](https://github.com/jolicode/castor)
- [License: MIT](https://github.com/jolicode/castor/blob/main/LICENSE)
- [Project website](https://castor.jolicode.com)
- [README](https://github.com/jolicode/castor/blob/main/README.md)
- [Releases](https://github.com/jolicode/castor/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jolicode-castor
