Codebattle: a Phoenix app that runs player code in per-request sandboxes
Codebattle - Game for programmers, where you solved algorithmic problems with other players in real time. You can fight with other players in single player mode or join massive tournaments.
At a glance
- What is it?
- Codebattle is an AGPL-3.0 licensed Elixir and Phoenix application, developed by the Hexlet community, where people solve algorithmic problems against each other in real time, either in single player or in large tournaments. Its interesting engineering is the execution layer: a Zig HTTP runner that checks solutions in separate privileged language containers while the app container stays unprivileged, and it suits a team that wants to run untrusted code safely or wants a working competitive programming platform to adapt.
- Who is it for?
- Codebattle fits a school or community that wants a working competitive programming platform, with a real multi-agent game loop, a live tournament structure and an execution layer that keeps untrusted player code out of the application container, and the Elixir and Phoenix stack is a good fit for a realtime application of that shape.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Elixir, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A game for programmers, built by the community that teaches them
Codebattle is described as a game for programmers, where you solve algorithmic problems with other players in real time, and you can fight with other players in single player mode or join massive tournaments.
The README is plain about its provenance: Codebattle is an open source game being developed by the Hexlet community, and the current version of the application is available at codebattle.hexlet.io. The repository is hosted under the hexlet-codebattle organisation and the topics list learn-to-code alongside phoenix, elixir, reactjs and docker.
That combination of a game and a teaching platform is the thing that makes the codebase worth reading. A competitive programming platform is one of the few applications that must simultaneously be a real-time game, a sandboxed code execution service and a learning product, and Codebattle has to solve all three. The judges of the design are the community that uses it, which is also why the contributing guidelines and the OpenCollective contributors badge are in the README rather than buried in a wiki.
The scale ambition is in the description too. Massive tournaments are a different engineering problem from a two-player match, because you have to deal with reconnection, with players who drop out mid-round, and with a scoreboard that has to stay consistent while that happens. A submission-based judge with a static test suite is a much easier thing to build, and the fact that this one is a game rather than a judge is visible throughout the repository.
The primary language is Elixir, with a Phoenix application, a React frontend, and Docker as the deployment substrate. The licence is AGPL-3.0, and the repository is not archived with the last push on 2026-10-19.
The execution layer: a Zig runner in privileged containers beside an unprivileged app
The mechanism that makes Codebattle safe enough to run is worth understanding in detail, because it is the part that most such projects get wrong.
The README states that solution checks use the Zig HTTP runner in separate language containers on the Compose network. The app container stays non-root and does not receive a Docker socket. The runner services are privileged to create their per-request sandboxes, and runner ports are not published to the host.
So the split is: the Phoenix application never executes player code, and never has the ability to. It talks over the Compose network to a per-language runner, and the runner, which is privileged, creates a sandbox per request, exactly as the production Kubernetes deployment does. The comment in compose.yml says the runner creates a sandbox per request, as in the Kubernetes deployment, and the runner is marked privileged with user 0:0 and a restart policy of unless-stopped.
The security reasoning in the README is explicit about the trade. Host Docker or Podman sockets and host credential directories are intentionally not mounted. The repository is writable inside the container, so the guidance is to keep only disposable development credentials in .env and never put production or personal cloud credentials in the workspace. The host .env is masked in the container and only variables explicitly allowlisted in compose.yml are passed through. Runner image build and publish targets still execute through the host container engine because they orchestrate other containers, and those targets are described as privileged operations to be used only with trusted code.
That is a coherent threat model, and it is the difference between a system designed to run strangers' code and one that was not. The app is the part you expose; the runners are the part that touches untrusted code and is kept off the host network.
The alternative design is the one many code judges use, where the application has a Docker socket and spawns a container per submission itself. That is simpler to wire up and much easier to get wrong, because a shell in the application process is a shell on the host.
Sixteen language slugs, and how you select them in development
The language support is broad and the configuration is done through one environment variable.
The supported slugs are clojure, cpp, csharp, dart, elixir, golang, java, js, kotlin, php, python, ruby, rust, swift, ts and zig. Sixteen languages, each with its own runner image.
In development you choose which of them to run with a comma-separated setting in the host .env:
CODEBATTLE_DEV_RUNNER_LANGS=js,python,cppThen you run make dev-up and restart make server. The language picker in the interface uses the same allowlist, so the development set and the selectable set stay in step, and a shell environment value overrides the .env file.
The production side has its own controls. CODEBATTLE_EXECUTOR=local selects direct Docker execution where Docker is available to the app, while the standard dev container uses the Zig services and production also defaults to Zig. CODEBATTLE_ZIG_RUNNER_URL_TEMPLATE overrides the Kubernetes URL pattern, and CODEBATTLE_WHITE_LIST_LANG_SLUGS limits the language picker.
The defaults in compose.yml show the shape of the thing. The app service gets CODEBATTLE_API_AUTH_KEY, the database connection variables pointing at a db host on port 5432, CODEBATTLE_DEVCONTAINER set to 1, the runner languages defaulting to js,python, the Zig runner URL template as http://runner-{lang}:4040/run, a live view salt, the port variables, a secret key base, and the Discord, GitHub and Firebase credentials with placeholder defaults. The template in that URL is the mechanism: one service name per language, one fixed port, and the app substitutes the language into the pattern.
The runner images themselves live in images/ at the repository root, with the shared language and checker code in lib/codebattle/runner/. The repository also has a k8s/ directory, an ansible/ directory, a terraform/ directory and a scripts/ directory, so the deployment story extends well past the local Compose setup.
make setup, and why your host toolchain does not matter
The install path is three commands, and the reason it works without any Elixir on your machine is the interesting part.
git clone [email protected]:hexlet-codebattle/codebattle.git
cd codebattle
make setupThe README explains what make setup does: it builds the pinned development image, installs Elixir and frontend dependencies inside it, starts PostgreSQL, and prepares the database. The line that matters for a newcomer is the one after: host installations of Elixir, Erlang, Node.js, pnpm, and Go are not used.
That is a deliberate design decision and it has two consequences. The good one is that a new contributor needs only Docker, and the requirements list is Mac or Linux plus Docker, with an optional editor that supports the Dev Container specification. The other consequence is that you cannot run mix or pnpm directly on the host, which is occasionally annoying, since every application command has to go through the container.
The Makefile makes that boundary explicit. Almost every target checks the CODEBATTLE_DEVCONTAINER environment variable and refuses or redirects depending on whether you are inside the container or on the host. dev-up calls the dev script, dev-build says already inside the dev container if you are, dev-down tells you to run it from the host, and dev-exec requires a CMD argument and prints a usage message if you leave it empty.
The setup target itself chains the pieces: setup-env copies .env.example to .env if .env does not exist, then setup runs dev-build, install and db-setup in order. install splits into install-mix, which runs mix deps.get with --check-locked, and install-pnpm, which installs the frontend dependencies with pnpm.
The lock check is worth noting. mix deps.get --check-locked means the build fails rather than silently resolving a different dependency version than the lock file records, which is the sort of discipline that makes a repository with a real test suite reproducible.
Day to day commands, and the port collision you will hit first
The Useful section of the README is a short list of make targets, and it covers the whole development loop:
make console
make dev-shell
make dev-exec CMD='mix upload_langs'
make db-migrate
make dev-rebuild
make dev-down
make clean # Removes the development containers and their persistent volumesmake console gives you the Phoenix console, make dev-shell opens a shell in the container, make dev-exec runs a single command inside it, and the rest manage the database and the containers.
The troubleshooting section addresses the one problem every new contributor hits, and it is worth reading before you hit it. The dev container publishes Phoenix on 127.0.0.1:4000 and Vite on 127.0.0.1:8081. If another project holds one of them, docker compose up fails with failed to bind host port, and the container never starts.
The fix has two parts. The devcontainer's initializeCommand and bin/dev run a .devcontainer/preflight script first, which names the offending process so you can stop it yourself. Or you move the dev container to free ports, and the variables for that are:
CODEBATTLE_PORT=4010
CODEBATTLE_FRONTEND_PORT=8091Both variables are read by Compose and by frontend/vite.config.ts, so the container and the dev server stay in sync, which is the detail that makes this work rather than merely work in theory.
The rest of the troubleshooting covers macOS and Linux Docker setup. On macOS, Docker Desktop via brew install --cask docker, and the advice to start it from the Applications folder or restart it if there are issues. On Linux, Docker Engine from the official guide, starting the service with sudo systemctl start docker or enabling it with sudo systemctl enable docker, and adding your user to the docker group with sudo usermod -aG docker $USER so you can run Docker without sudo, followed by a logout and login or newgrp docker.
The test and lint targets are equally conventional: make test and make test-fe for the two suites, make lint, make credo for the Elixir static analysis, and make check-js for the frontend.
A single Phoenix app, and an AGPL licence to think about
The repository is one Phoenix application rather than a set of services, and the README says so directly. Codebattle is a single Phoenix app, with lib/, test/, priv/, frontend/ and static/ live at the repository root, and the shared language and checker code in lib/codebattle/runner/, with the language image sources in images/.
The root also holds the files that tell you how the project is operated. There is a Containerfile.dev and a Containerfile.codebattle, a compose.yml, a Makefile, mix.exs and mix.lock, a .devcontainer/ directory, .credo.exs for the Elixir static analysis, .formatter.exs, a .githooks/ directory, an ansible/ directory with ansible.cfg, a terraform/ directory, a k8s/ directory, an nginx.conf with an nginx-assets-entrypoint.sh, a mise.toml, a coveralls.json for coverage reporting, and both AGENTS.md and CLAUDE.md for coding agents, along with an .mcp.json file.
That is a substantial operations surface for one application, and it reflects the ambition. The terraform and ansible directories suggest a real hosting story, and the k8s/ directory is where the per-language runner deployment lives, which is the piece the whole safety model depends on.
The licence is AGPL-3.0, which is the one thing to think carefully about before adopting this for anything other than running the existing instance. The AGPL's network clause means that a modified version offered to users over a network must be made available under the same terms. For a school running its own instance for its own students, that is not a problem. For a team that wants to fork the platform, rebrand it and run it as a service, it is a serious constraint, and the commercial alternative would be a conversation with Hexlet rather than a code change.
This review does not interpret licence terms, and it is not legal advice. It is worth noting, though, that the project is the product of a community school, and the licence is the same one you would expect for software that is meant to be run by institutions rather than sold as a product.
On maintenance, the repository is not archived, the last push was on 2026-10-19, and the default branch is master. The recent releases list is empty, because the repository has no GitHub releases, so there is no version to pin and no changelog entry to read before an upgrade.
Editorial conclusion
Codebattle fits a school or community that wants a working competitive programming platform, with a real multi-agent game loop, a live tournament structure and an execution layer that keeps untrusted player code out of the application container, and the Elixir and Phoenix stack is a good fit for a realtime application of that shape. It is the wrong choice if you want a code judge you can point at any problem set, since the problem content and the Hexlet community are part of the product, and it is the wrong choice if you cannot run Docker, since the whole execution model is built on per-request sandbox containers. Before you deploy it, read the security notes in the README about not mounting the Docker socket and keeping only disposable credentials in .env, and check the AGPL-3.0 terms against how you intend to run it.
Frequently asked questions
How do I set up Codebattle locally?
Clone the repository, cd into it and run `make setup`, which builds the pinned development image, installs Elixir and frontend dependencies inside it, starts PostgreSQL and prepares the database. The requirements are Mac or Linux with Docker, and host installs of Elixir, Erlang, Node.js, pnpm and Go are not used.
How does Codebattle run untrusted player code safely?
Solution checks use the Zig HTTP runner in separate language containers on the Compose network. The app container stays non-root and does not receive a Docker socket, the runner services are privileged to create their per-request sandboxes, and runner ports are not published to the host.
Which programming languages does Codebattle support?
The README lists clojure, cpp, csharp, dart, elixir, golang, java, js, kotlin, php, python, ruby, rust, swift, ts and zig. In development you choose which runners to start with the comma-separated CODEBATTLE_DEV_RUNNER_LANGS variable, and the language picker uses the same allowlist.
What do I do if the dev container will not start because a port is taken?
The dev container publishes Phoenix on 127.0.0.1:4000 and Vite on 127.0.0.1:8081, and a preflight script names the process holding the port. Alternatively set CODEBATTLE_PORT and CODEBATTLE_FRONTEND_PORT in .env, which are read by both Compose and frontend/vite.config.ts.
Is Codebattle still being developed and under what licence?
The repository is not archived and the last push was on 2026-10-19, on the master branch, and it has no GitHub releases. The licence field on the repository says AGPL-3.0, and the README describes the project as an open source game developed by the Hexlet community.
Official sources
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.
[](https://hysenlabs.com/projects/hexlet-codebattle-codebattle)Was this useful?