# erlexec: running OS processes from the BEAM with real control

> erlexec is an Erlang application with a C++ port program that gives Erlang processes fine grained control over OS child processes, covering pty support, process groups, effective users, Linux capabilities, cgroups, output redirection and linking OS process lifetime to Erlang pids. It exists because erlang:open_port/2 cannot express most of that.

**saleyn/erlexec** — Execute and control OS processes from Erlang/OTP

- Repository: https://github.com/saleyn/erlexec
- Website: https://hexdocs.pm/erlexec/readme.html
- Stars: 606 · Forks: 155
- Language: C++
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/saleyn-erlexec

## Why open_port is not enough

erlexec exists to execute and control OS processes from Erlang and OTP, and the readme states the reason plainly. It provides significantly better control over OS processes than the built-in erlang:open_port/2 command with a {spawn, Command} option, and performs proper OS child process cleanup when the emulator exits.

That last clause is the one that matters most in production. The standard port program gives you a way to run a command and talk to it, and it gives you a pid. What it does not give you is control over what happens to the child when your Erlang process, or the whole node, goes away. An orphaned process holding a port, a database connection or a file lock is the kind of problem that only shows up in an incident.

The implementation is described as an Erlang application with a C++ port program that gives light-weight Erlang processes fine-grain control over execution of OS processes. So the shape is the familiar one: an Erlang side that owns the API and the supervision, and a native port program that does the fork, exec, wait and signal work that the BEAM cannot do itself.

That division is also why the project is worth reading even if you never use it. The list of things a port program can do that a plain port cannot is effectively a list of what the operating system offers and the BEAM deliberately does not expose, and it is a useful catalogue of the gap.

The author is Serge Aleynikov, the primary language of the repository is C++, and the project is published on Hex.pm as erlexec, with the documentation on hexdocs. The recent releases are 2.5.0 from 2026-09-17, 2.4.1 from 2026-09-09 and 2.4.0 from 2026-09-02, with the last push on 2026-10-10. The readme says the application has been in production use by Erlang and Elixir systems and is considered stable.

## The feature list, and what each entry buys you

The supported features are listed in the readme, and grouping them by what problem they solve makes the library easier to evaluate.

Process lifecycle and control come first. You can start and stop OS commands and get their OS process IDs and termination reason, meaning exit code, signal number and core dump status. You can set the OS command's working directory, environment, process group, effective user and process priority. You can manage and monitor externally started OS processes, and execute OS processes synchronously or asynchronously. A custom termination command can be provided for killing a process, or you can rely on the default SIGTERM and SIGKILL behaviour, and you can specify a custom timeout for SIGKILL after the termination command or SIGTERM was executed and the child is still alive.

Linking the two process models is the piece that makes it feel like an OTP library rather than a wrapper. Erlang processes can be linked to OS processes via intermediate Erlang pids that are linked to an associated OS process, so that termination of an OS process results in termination of an Erlang pid and vice versa. Termination can also be monitored using erlang:monitor/2, which means you can use the standard OTP monitoring patterns rather than inventing your own.

Group and session control is the other structural feature. You can terminate all processes belonging to an OS process group, and kill processes belonging to an OS process group at process exit, so a child that spawns grandchildren does not leave them running.

Input and output are covered too. You can communicate with an OS process via its STDIN, redirect STDOUT and STDERR to a file, an Erlang process or a custom function, and when redirected to a file the file can be opened in append or truncate mode and given an access mask at creation.

And there is support for OS commands with unicode characters, which is the sort of detail that turns out to matter the first time a path with a non-ASCII character goes through a naive implementation.

## PTY support, credentials and cgroups

Three of the features go beyond process management into the territory of running a workload properly, and they are where the C++ port program earns its keep.

The first is pseudo terminal support, for running interactive processes. The readme says it supports all the RFC4254 pty pseudo-terminal options defined in section 8 of the spec, which is the SSH request for a pseudo-terminal, the full set of terminal modes and window size parameters. It also flags an important note about PTY stdout and stderr separation, which is the known sharp edge of pseudo terminals: the two streams are merged on a pty, so a program that writes diagnostics to stderr cannot be distinguished from one writing to stdout. There is a dedicated section in the table of contents for that caveat.

The readme also explains that PTY commands use the session and process group created by setsid(), so {group, 0} remains the equivalent of own process group in PTY mode. That is a small detail with real consequences, because the process group semantics you rely on for cleanup behave differently under a pty unless you account for it.

The second is credentials. OS processes can be executed under different user credentials using Linux capabilities, and there is a substantial set of sections covering it: running the exec-port as another effective user, allowing the exec-port to run commands as other effective users, the available capabilities, using specific capabilities, using all capabilities, using default capabilities, combining with user switching, and capability propagation.

The third is cgroups. Spawned processes can be attached to Linux cgroups for resource isolation and control. The readme ties this to the reason the project exists in modern infrastructure: with cgroup support, erlexec is a robust tool for running untrusted or resource-intensive workloads safely within constrained environments, such as containerized or multi-tenant systems.

That is the clearest statement of the library's value. Running an untrusted command is exactly the case where a bare port is not enough, because you need to bound what the command can consume.

## Building it, and the rebar3 plus native build

The build is rebar3, and the Makefile is a thin convenience layer over it.

```makefile
all:
	@$(REBAR) compile

clean:
	@$(REBAR) $@
	@rm -rf ebin erl_crash.dump _build
```

The Makefile is a small wrapper: the all target compiles through rebar3, clean removes the build output along with the crash dump, test runs the eunit suite with verbose output, doc and docs run ex_doc, and publish does a hex publish with an optional replace flag. There is a deprecate target that retires a version on Hex with a message, and a tar target that builds a source tarball while excluding the core directories and the build artefacts.

The version number is derived from git rather than being written down, using git describe with the tags and a sed to strip the leading v, so the tarball name always matches the tag it was cut from.

Two targets are for the native side specifically. The info target delegates to the c_src directory's own Makefile, which is where the C++ port program is built, and the test-debug target runs the eunit suite with OPTIMIZE=0 so a failing test can be inspected without optimisation. The dialyzer target builds a PLT over erts, kernel and stdlib and then runs the analysis with -nn.

The repository layout is the standard Erlang one, with src, include, priv, c_src, test, and a rebar.config plus a rebar.config.script and a rebar.lock, and the Makefile errors out early if neither rebar3 nor rebar is on the path. There is also an AUTHORS file, a CHANGELOG.txt, a RELEASE.md, a TODO, and an assets directory with the banner image.

The supported platforms are listed as Linux, Solaris, FreeBSD, DragonFly, OpenBSD and MacOS X. That is a wide list, but the cgroup and capabilities features are Linux specific, so the portability is real for the core process control and partial for the isolation features.

## Using it, and where the documentation actually lives

The readme is a summary, and it says so: the documentation is at hexdocs.pm/erlexec/readme.html. The table of contents in the readme gives a sense of how much there is, with sections for starting and stopping an OS process, clearing the environment or unsetting an environment variable of the child process, running the exec-port as another effective user, Linux capabilities, cgroup support, running the port program as root, killing an OS process, using a custom success return code, redirecting stdout to a file, to the screen, to an Erlang process or to a custom function, appending stdout to a file, setting up a monitor for the OS process, managing an externally started OS process, specifying a custom process shutdown delay in seconds, specifying a custom kill command, communicating via STDIN with and without sending end-of-file, running commands synchronously, running with or without a shell, running with a pseudo terminal, and killing a process group at process exit.

That list is a good map of the API surface, and it tells you the library is opinionated about process management rather than being a thin spawn call.

For Erlang, the dependency goes into rebar.config:

```erlang
{deps,
 [% ...
  {erlexec, "~> 2.0"}
  ]}.
```

And the application has to be included in the .app.src file. For Elixir, the readme has a parallel section for importing it as a dependency.

The donation section is worth a mention only because it is unusual to see: the project lists a Bitcoin address and an Ethereum address, and asks for donations if you find it useful. That is a small signal about how the project is funded, and for a library maintained by one person over many years it is a reasonable arrangement.

The practical entry point for an evaluation is therefore the hexdocs documentation rather than the readme, and the example sections there, especially the ones on custom kill commands, custom shutdown delays and stdout redirection, are the parts most likely to change how you structure your supervision.

## Conclusion

Adopt erlexec if you run OS workloads from Erlang or Elixir and need more than open_port gives you, especially pseudo terminals, output redirection to a file or a function, process group control, or running under a different user or in a cgroup. Do not adopt it if all you need is a command that runs to completion and returns its output, because the port program is a native component and adds a build dependency. Before you commit, read the architecture section in the documentation, confirm your target platform is on the supported list, and test the cleanup behaviour on exit, since orphaned child processes are the failure mode that matters most in production.

## FAQ

### What is erlexec?

It is an Erlang application with a C++ port program that gives light-weight Erlang processes fine-grain control over the execution of OS processes. It provides significantly better control than the built-in erlang:open_port/2 with a {spawn, Command} option, and performs proper OS child process cleanup when the emulator exits.

### What can erlexec do that erlang:open_port cannot?

It returns OS process IDs and the termination reason, sets the working directory, environment, process group, effective user and priority, redirects STDOUT and STDERR to a file, an Erlang process or a custom function, links OS processes to Erlang pids so termination propagates both ways, supports pseudo terminals, Linux capabilities and cgroups, and kills a whole process group at exit.

### Which platforms does erlexec support?

Linux, Solaris, FreeBSD, DragonFly, OpenBSD and MacOS X. The Linux specific features, meaning capabilities and cgroup support, are the ones that will not carry to the others.

### How do I add erlexec to an Erlang project?

Add the dependency to rebar.config as {erlexec, "~> 2.0"} and include the application in the .app.src file. The Elixir import instructions are in the same documentation. The library is published on Hex.pm as erlexec.

### Is erlexec suitable for running untrusted workloads?

The readme says that with Linux cgroup support, erlexec is a robust tool for running untrusted or resource-intensive workloads safely within constrained environments such as containerized or multi-tenant systems. The project is described as having been in production use by Erlang and Elixir systems and as considered stable.

## Sources

- [Issues](https://github.com/saleyn/erlexec/issues)
- [Project website](https://hexdocs.pm/erlexec/readme.html)
- [README](https://github.com/saleyn/erlexec/blob/master/README.md)
- [Releases](https://github.com/saleyn/erlexec/releases)
- [saleyn/erlexec on GitHub](https://github.com/saleyn/erlexec)

---

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