Forgejo Runner v13.1.0 is available
Forgejo Runner v13.1.0 was released on August 31st, 2026. This minor feature release includes a new experimental plugin capability, changes to commands running in a terminal, preparations for a Forgejo bug-fix, and a variety of routine dependency upgrades.
The complete release notes are at v13.1.0’s release page.
Plugin Capability
Forgejo Runner currently supports running jobs in three different execution engines: within Docker/Podman containers, within LXC containers, and without any containers running directly on the host. Many community members have expressed interest in adding new execution engine capabilities to Forgejo Runner, such as running jobs in Firecracker microVMs, Kubernetes pods, QEMU/libvirt VMs, systemd-nspawn containers, and directly on cloud hyperscalers. However, the Forgejo development team doesn’t have the capacity to build all of these execution engines and provide long-term support for them.
Forgejo Runner now supports a plugin execution engine that allows these execution engines to be created as external applications and to be used by jobs running on a Forgejo Runner.
Forgejo Runner v13.1.0 is the first release with this plugin engine. The plugin engine operates by connecting to a running daemon via the gRPC protocol, and executing a series of RPC interactions to create environments, run commands, capture output, and cleanup. This capability is currently considered experimental; the protocol is considered an alpha release.
As an experimental feature, the Forgejo team is not fully committed to this feature as a permanent addition. We believe it is a good direction, and we believe we can continue to iterate it to a fully supportable and capable feature based upon feedback from ourselves and other users. But as that feedback is collected and incorporated, we leave open the possibility that we may gain new knowledge and be required to change direction.
As an alpha protocol, we consider the protocol is subject to change with no stability guarantees at the moment. As we collect feedback from the community on the protocol, we may find that design decisions need to be revised in order to meet the broad and flexible needs of all the possible execution engines that could use the plugin. But we feel that it’s in a good place to start development with, and that it isn’t likely to encounter major protocol changes.
Related development work:
- https://code.forgejo.org/forgejo/runner/pulls/1500
- https://code.forgejo.org/forgejo/runner/pulls/1713
- https://code.forgejo.org/forgejo/runner/pulls/1719
- https://code.forgejo.org/forgejo/runner/pulls/1710
- https://code.forgejo.org/forgejo/runner/pulls/1705
- https://code.forgejo.org/forgejo/runner/pulls/1686
How a Plugin Works
A plugin is an application that exposes the plugin.v1alpha gRPC service, typically on a local network port or UNIX
socket. This service is defined in Forgejo Runner’s
plugin.proto. The
plugin must also implement the standard
grpc.health.v1 gRPC service.
Once a plugin application is running, it needs to be added to Forgejo Runner’s configuration file in the plugins
section. For example:
log: # ...
runner: # ...
server: # ...
# Add a plugins section:
plugins:
my-plugin: # the plugin name here is used in labels, coming up next
address: 127.0.0.1:50051 # can also be a unix socket - unix:///run/my-plugin.sock
options:
# `options` is an arbitrary map of string -> string. Values configured
# here are provided to the plugin in the `CreateRequest` message. Their
# purpose are defined by the plugin.
option_a: value_b
Once a plugin is defined, it can be used by defining labels. A label matches the value of a runs-on: ... definition in
a Forgejo Action job, and defines what execution engine to run the job within, and optionally additional information for
the execution engine. Plugin labels work the same way.
runner:
labels:
- 'docker:docker://node:current-bookworm' # typical docker label
- 'bare-metal:host' # typical "host" executor label
# When a job is defined as `runs-on: my-plugin-a`, then this label will be
# matched. The plugin `my-plugin` will be used to execute the job. The
# value `some-parameter` will be passed to the plugin in the
# `CreateRequest` message, as the field `image`. The `image` value can be
# overridden by the `jobs.<job_id>.container.image` value in the workflow.
- 'my-plugin-a:my-plugin://some-parameter'
Having defined a plugin, and a label to use that plugin, jobs can now be configured to runs-on: my-plugin-a. For
example:
on:
pull_request:
jobs:
test-job:
runs-on: my-plugin-a
steps:
- id: stepwithoutput
run: |
echo "myvalue=outputvalue1" >> $FORGEJO_OUTPUT
- run: echo "previous myvalue was ${{ steps.stepwithoutput.outputs.myvalue }}"
When this job is executed, the plugin protocol will follow these steps; the details provided and returned from the RPC calls is documented in the protocol definition file:
CapabilitiesRPC will be invoked in order to see the capabilities of the plugin.CreateRPC will be invoked in order to create an execution environment.StartRPC will be invoked to start the environment. This RPC can stream data back to Forgejo Runner, which will appear in theSet up jobsection in the Forgejo Actions log UI.CopyInRPC will be invoked in order to store a script into the execution environment, representing the first step,echo "myvalue....ExecRPC will be invoked once to execute the script uploaded to the environment byCopyIn.CopyOutRPC will be invoked multiple times to read per-step files, such as the one written to by$FORGEJO_OUTPUT.CopyIn,Exec, andCopyOutwill be repeated for each step in the workflow.- When an action like
uses: actions/checkout@v4is executed, aCopyInoperation is performed that provides the entire action codebase.
- When an action like
RemoveRPC will be invoked at the end of the job to tear down the execution environment and free all related resources.
Example Implementations
Two example implementations were developed during the plugin creation in order to validate and test capabilities, and may be useful to other developers as reference implementations.
Forgejo Runner contains a testplugin, which is written in Go. It operates similarly to the host-based executor, as it runs commands on the current host in a temporary directory.
runner-systemd-nspawn is a Rust application which permits running jobs in systemd-nspawn containers.
Neither of these example implementations are suitable for production usage.
Commands with Attached Terminal (Breaking)
Forgejo Runner does not operate as a terminal when it runs a job. When it collects logs and they are displayed in Forgejo, a small subset of ANSI text formatting sequences are supported; but, terminal control commands such as moving the cursor, clearing the line, clearing the screen, and so on are not supported. Despite these limitations, Forgejo Runner has often been signaling to commands that they are running within a terminal by attaching a pseudo-tty (pty) to the process’s stdout and stderr outputs. Depending on the command being run, this can result in a large amount of garbage data being generated to the logs of a Forgejo Action.
In Forgejo Runner v13.1.0, these terminal attachments are removed.
For most commands executed by a job, this will have no impact. Some commands detect whether stdout is a console and
will only use ANSI colour sequences when it is, and those commands will no longer have colourful output. Some commands
support a quasi-standardized FORCE_COLOR=1 environment variable to restore colour output
even when not using a terminal.
For a small subset of commands, the removal of the terminal attachment will break their execution. For example, running
a workflow which executes docker run -it ... would attach a terminal to a Docker container, and would fail if you’re
not currently in a terminal. This specific command can be fixed by removing the -t option. This was not an anticipated
side-effect of this change, but has been reported to the Forgejo
team since the release.
Related changes: