Open-source project
engineer-man/piston avatar
engineer-man/piston

Piston: self-hosting an untrusted code execution engine

A high performance general purpose code execution engine.

2,823 stars468 forksJavaScriptMIT

At a glance

What is it?
Piston runs untrusted code inside Docker containers and exposes it over a small HTTP API. The public endpoint now needs an authorization token, so most teams should plan to run their own instance.
Who is it for?
Adopt Piston if you need to run untrusted code on hardware you control and can accept the isolation model the repository documents, with runtimes installed explicitly through cli/index.js ppman install. Do not adopt it if you need the hosted API for a commercial or individual project: the README states keys are not granted for those cases, and the public API has required authorization since Feb 15, 2026.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 64 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Piston is for, and who it is not for

Piston describes itself as a high performance general purpose code execution engine. The README states it excels at running untrusted and possibly malicious code without fear from any harmful effects. That sentence is the whole product: you give it a language, a version and a source file, and it gives you back the output of running that file. The README lists EMKC Challenges, EMKC Weekly Contests, the Engineer Man Discord server, web IDEs and 200+ direct integrations as places where it is used, and the official extensions include a Discord bot for code evaluation, a CLI shell, and client wrappers for Node.js, Java, Python, Go and Rust.

The audience follows from that. If you are building a judge for programming exercises, a playground where users paste code you did not write, or a bot that evaluates snippets on demand, Piston is aimed at you. If you want to run your own build scripts or a notebook for yourself, it is heavier than the problem requires. The README also makes clear that the hosted service is not a free-for-all: as of Feb 15, 2026 the public API requires an authorization token, granted at the maintainer's discretion for non-commercial educational projects, and the README lists the categories that will not get a key, including projects that cost money, temporary projects, individual projects, portfolio projects, school assignments and conceptual projects. Read that list before you plan around the hosted endpoint. For everyone else, the README's answer is to run your own instance.

How the API and the container fit together

The architecture visible in the repository is three pieces. A Docker image (ghcr.io/engineer-man/piston) runs the API service, which listens on port 2000 by default. A CLI in the cli/ directory talks to that API to install language runtimes and to submit jobs. Language packages live in packages/ and are mounted into the container, which is why the compose file maps ./data/piston/packages to /piston/packages. The API surface is two endpoints: GET /api/v2/runtimes returns the supported languages with their versions and aliases, and POST /api/v2/execute runs a job.

The runtimes response carries a language name, a version and a list of aliases, and the README notes that multiple versions of the same language may be present at the same time and selected per job. That is the design decision worth noticing: runtimes are installed artifacts, not baked into the image. A fresh container comes up with no language runtimes installed at all, and the CLI is what populates it. It means the image stays small and you only pay for the languages you use, but it also means a deployment is two steps, not one, and a container that loses its mounted packages volume loses its languages with it.

The compose file also shows the isolation posture. The api service runs with privileged: true, and mounts a tmpfs at /tmp with the exec option. Privileged mode is what lets the container do the sandboxing work the README's security section implies, and it is also the reason you should not casually run this alongside other workloads on a shared host. The README does not document a non-privileged mode.

Installing Piston with docker-compose

The all-in-one path needs Docker, Docker Compose, Node.js 15 or newer, and cgroup v2 enabled with cgroup v1 disabled. Clone the repository first, and note the README's warning that the clone must have LF line endings.

bash
git clone https://github.com/engineer-man/piston

From the repository root, start the API container and install the CLI dependencies. The API comes up with no language runtimes installed.

bash
docker-compose up -d api
cd cli && npm i && cd -

If you do not want the CLI, the README gives a plain docker run form that publishes port 2000 and mounts the current directory at /piston.

bash
docker run \
    --privileged \
    -v $PWD:'/piston' \
    -dit \
    -p 2000:2000 \
    --name piston_api \
    ghcr.io/engineer-man/piston

There is also a ./piston start script for building the containers when you are testing packages locally, with ./piston help for other subcommands.

Installing a runtime and running your first job

The CLI is executed as cli/index.js. List what is available, then install a runtime; the README's examples use python and show that a version can be pinned or left as a range.

bash
cli/index.js ppman list
cli/index.js ppman install python
cli/index.js ppman install python=3.9.4

Running a script goes through the same CLI. The README writes a file and runs it against the latest version, then shows the -l flag selecting 3.9.4, then 3.x, then 3.

bash
echo 'print("Hello world!")' > test.py
cli/index.js run python test.py
cli/index.js run python test.py -l 3.9.4

Against a remote instance, the -u flag takes the base URL, which is the same port 2000 the container publishes.

bash
cli/index.js -u http://piston.server:2000 ppman list

Direct API use follows the same two endpoints. The README's runtimes example shows a JSON array where each entry has a language, a version and aliases, and states that either the name or an alias must be supplied with the version when calling /api/v2/execute. The README does not print a full execute request body in the excerpt available, so check the Runtimes and Execute sections of the documentation before you write a client.

The limitations that decide whether this fits

The sharpest constraint is the public API. Since Feb 15, 2026 it is no longer freely available, and the README's list of disqualifying project types is unusually blunt. If your use case is a paid product, a portfolio piece or a course assignment, the README tells you the answer is no and points you at self-hosting. That is not a limitation of the software, but it is a limitation of the easiest deployment path, and it is the thing most people will hit first.

The second constraint is the host. cgroup v2 must be enabled and cgroup v1 disabled, which rules out older distributions and any environment where you cannot change kernel boot parameters. The API container runs privileged, so a shared host with other tenants is a poor fit. The README does not document a fallback for hosts that cannot meet these conditions.

Third, runtimes are not in the image. A container started without the CLI has an empty language set, and the compose file persists packages to ./data/piston/packages, so an ephemeral container loses everything installed into it. The README does not document rollback for a failed package install, nor does it describe what happens to in-flight jobs when a runtime is upgraded underneath them. Treat runtime versions as deployment state you manage deliberately.

Finally, this is the wrong tool if your code is trusted. Piston's value is the isolation boundary; if you are running your own code on your own machine, a subprocess call is simpler and has fewer moving parts than a privileged container with a package manager in front of it.

Alternatives and where the approach differs

The closest alternative in the same problem space is Judge0, which is also a self-hosted code execution service with an HTTP API and a runtime catalogue. The difference in approach is in how the sandbox is built. Piston ships as a single Docker image that runs privileged and manages language packages through its own CLI, so the unit of deployment is one container plus a packages volume. Judge0's documentation describes a multi-container topology with separate services for the API, workers and the database, which gives you more knobs for scaling submission throughput but more infrastructure to operate. If your load is a handful of requests per second from a Discord bot or a classroom, Piston's single container is the shorter path. If you are running a public judge with a queue and a database of submissions, the extra services are doing work you would otherwise have to build.

For the narrower case of running one language, a plain container per execution with a seccomp profile is a legitimate alternative and gives you full control over the runtime image. You give up the version catalogue, the alias resolution and the uniform request format that Piston provides across languages. That trade is worth it when you support exactly one language and never intend to add another.

Maintenance, licence and the upgrade question

The repository is not archived, and the last push was on 2026-07-31, which is recent enough that the project is still receiving commits. The only release listed is a packages release from 2021-03-15, so treat the master branch, not a tagged release, as the thing you deploy. That has a practical consequence: pinning to a tag is not an option the repository offers for the engine itself, and the docker-compose file references the ghcr.io/engineer-man/piston image rather than a version tag, so a pull can move you forward without warning. If you need reproducibility, pin the image by digest in your own compose file.

Upgrades have two independent axes. The engine image is replaced by pulling a new one and recreating the api service. Runtimes are separate artifacts installed through cli/index.js ppman install and stored in the packages volume, so an engine upgrade does not automatically change your language versions, and a runtime install does not require restarting the container. That separation is convenient but it means the set of languages in production is defined by whatever was last installed into the volume, not by the image.

The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are included. That is a statement about the licence text, not legal advice; if you are embedding Piston in a product, have your own counsel read the license file at the repository root. Note the distinction between the software licence and the hosted service: the MIT licence covers the code you run yourself, and it says nothing about getting a key for emkc.org.

Editorial conclusion

Adopt Piston if you need to run untrusted code on hardware you control and can accept the isolation model the repository documents, with runtimes installed explicitly through cli/index.js ppman install. Do not adopt it if you need the hosted API for a commercial or individual project: the README states keys are not granted for those cases, and the public API has required authorization since Feb 15, 2026. Before committing, verify on your own host that cgroup v2 is enabled and cgroup v1 is disabled, that the api service comes up with the privileged flag from docker-compose.yaml, and that a job submitted to POST /api/v2/execute returns the output you expect for the runtime you installed.

Frequently asked questions

How do I install Piston on my own server?

Clone the repository, run docker-compose up -d api from the root, then install the CLI dependencies with cd cli && npm i && cd -. The API starts with no language runtimes, so add them afterwards with cli/index.js ppman install. Host dependencies are Docker, Docker Compose, Node.js 15 or newer, and cgroup v2 enabled with cgroup v1 disabled.

How do I run a script through the Piston CLI?

The README writes a file and passes it to cli/index.js run with the language name, for example cli/index.js run python test.py. The -l flag selects a specific version such as 3.9.4, 3.x or 3. Against a remote instance, add -u followed by the base URL, for example http://piston.server:2000.

Does Piston come with language runtimes preinstalled?

No. The README states the API will be online with no language runtimes installed, and runtimes are added through the CLI with ppman install. The docker-compose file persists them under ./data/piston/packages, so an ephemeral container without that volume loses them.

Is the public Piston API still free to use?

No. The README states the API is no longer freely available to the public as of Feb 15, 2026, and that an authorization token must be obtained. Keys are only granted for good cause non-commercial educational projects, and the README lists several categories, including paid and individual projects, that will not receive one.

What endpoints does the Piston API expose?

Two. GET /api/v2/runtimes returns the supported languages with versions and aliases, and POST /api/v2/execute runs a job. The container exposes the API on port 2000 by default, and the CLI uses the same endpoints for package management and job execution.

Official sources

  1. engineer-man/piston on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/engineer-man-piston.svg)](https://hysenlabs.com/projects/engineer-man-piston)