Getting Started
Pomelo
What is Pomelo?
Pomelo is an open-source competitive programming contest platform for hosting coding contests with automated code evaluation powered by Citron, its sandboxed execution engine.
It provides the pieces needed to run a local contest environment, including a participant-facing web app, an Express API, admin tooling, and Docker services for the backing stack.
Use this documentation to install dependencies, configure environment variables, start the services, and troubleshoot setup issues.
Installation
Run the following command to automatically install and set up Pomelo:
During Installation
When you run the installation script, it will prompt you to select which version of Pomelo to install (defaults to the latest release).
Next Steps
Once the installation finishes and the Pomelo daemon is running, you can start the entire platform with:
To access the built-in administrator interface to manage your instance, simply run:
Workspace Setup
Follow these steps to set up the Pomelo workspace on your local machine.
Prerequisites
The Pomelo stack requires specific system tools to run locally and build. Make sure the following are installed:
- Node.js (
node): Required for the Next.js frontend and Express backend. - Bun (
bun): Used specifically to compile the CLI and Daemon binaries. - pnpm: The strict package manager used across the monorepo workspace.
- Docker & Docker Compose: Required for the database and the Citron execution environment.
1. Clone the repository
Clone the repository to your local machine:
2. Install workspace dependencies
Run pnpm install at the repository root to install all workspace dependencies.
3. Set up environment variables
Copy .env.example to .env:
4. Start development mode
Run pnpm dev to start the development environment:
Environment Variables
Developers must copy the template .env.example file to .env before running the stack. Once .env is set up, running pnpm dev automatically initializes all services and Docker containers.
Development Variables
| Variable | Default Value | Description |
|---|---|---|
AUTH_SECRET | (required) | Long random secret used by NextAuth for session encryption. |
PORT | 8080 | Port the Express API listens on. |
MONGODB_URI | mongodb://localhost:27017/pomelo | MongoDB connection string. |
CITRON_URL | http://citron:2358 | URL of the Citron execution engine. |
CITRON_TOKEN | (optional) | Sent as the X-Judge-Token header. Only needed if Citron is configured with an auth token. |
DOMAIN | localhost:3000 | Frontend domain used by the Next.js client. |
PROTOCOL | http | Protocol for the frontend app. |
BACKEND_URL | http://localhost:8080 | URL used by server-side code to call the backend. |
AUTH_SECRET, MONGODB_URI, and CITRON_URL are required; the stack refuses to start without them.
Dev Setup
Docker Setup
Running pnpm dev automatically handles setting up and starting all required Docker containers (MongoDB and Citron).
If you prefer to manually control or inspect the backing services, Pomelo includes Docker Compose definitions in docker/app/ and docker/citron/.
Manual Container Management
docker compose --project-name pomelo --env-file .env \
-f docker/app/docker-compose.dev.yaml \
-f docker/citron/docker-compose.dev.yaml \
--project-directory . up mongo citron -d Services
mongo MongoDB database for contest and user data.
server Express API for auth, contests, and submissions.
client Participant-facing web app.
citron Sandboxed execution engine that compiles and runs submissions.
caddy Reverse proxy and automatic SSL.
Citron runs untrusted code, so it publishes no port in production and is reached only over the internal Compose network at http://citron:2358. In development it binds to 127.0.0.1:2358 for inspection.
Stop containers
Remove volumes
Local Installation
Packaging Script
The script at scripts/package.sh compiles the workspace into a standalone build.tar.gz archive containing only pre-built artifacts.
Usage
Run the script without arguments to create the default archive:
Or specify a custom output filename:
Installation Script
The script at scripts/install.sh deploys Pomelo to a host machine under /opt/pomelo.
Usage
To install from a local tarball:
Uninstalling
To cleanly remove the installation:
Admin Tooling
Pomelo CLI
The admin directory contains the source for the administrator tooling.
The CLI and daemon are written in TypeScript and compiled into single-file executables using Bun. You must have Bun installed to compile these tools from source.
Daemon
pomelod is the background orchestrator daemon for Pomelo. Once installed, it monitors contest states and manages background tasks.
Systemd Policy
During installation, Pomelo sets up a persistent systemd service for the daemon at /etc/systemd/system/pomelod.service.
This policy ensures that the daemon automatically starts on boot and restarts on failure. If systemd is unavailable on the host system or the installation is not run as root, the daemon will start as a standard non-persistent background process instead.
CLI
pomelo is the terminal utility for administrators. It is symlinked to /usr/local/bin/pomelo during installation and provides a centralized way to interact with the platform services.
Commands
| Command | Description |
|---|---|
pomelo start | Starts all Pomelo backend services. |
pomelo stop | Stops the running Pomelo services. |
pomelo restart | Restarts the Pomelo services. |
pomelo status | Checks the status of the daemon and Docker containers. |
pomelo logs | Streams logs from the running services. |
pomelo ui | Opens the web admin dashboard on port 8462. |
pomelo uninstall | Uninstalls Pomelo and cleans up the systemd service. |
CLI Development
This section covers how to work on the CLI locally, which is located in the @pomelo/admin package.
Viewing the UI
To start the Vite UI on port 8462:
Building Binaries
To compile the TypeScript files into pomelo and pomelod binaries using Bun:
Hot-Swapping (Testing Local CLI)
You can test local changes by running the hot-swap script.
WARNING: This script requires sudo privileges. You must have already installed Pomelo to /opt/pomelo using install.sh first. This script only copies the newly built binaries over the existing installation and restarts the pomelod systemd service.
Troubleshooting
Common Issues
Open the issue that matches what you are seeing, then compare the guidance with your local setup.
Toolchain pnpm install fails
Confirm pnpm 10.0.0 or newer is installed.
Confirm Node.js is installed.
Confirm Git is available in PATH.
Environment pnpm dev fails
Confirm .env exists (cp .env.example .env).
Confirm Docker desktop/daemon is running so containers can be started automatically.
Confirm AUTH_SECRET is set to a non-empty value.
Containers Docker containers fail to start
Confirm Docker and Docker Compose v2 are installed and running.
Confirm .env exists in the repository root.
Confirm AUTH_SECRET, MONGODB_URI, and CITRON_URL are set.
Check container status or run pnpm dev to trigger automated setup.
Database Backend cannot connect to MongoDB
Verify MONGODB_URI is correct and reachable.
Confirm the mongo container or service is running.
Check logs with docker logs pomelo-mongo-1.
Citron Submissions fail or time out
Verify CITRON_URL is correct (http://citron:2358 when self-hosted).
Confirm the citron container is running with pomelo logs citron.
If Citron is configured with an auth token, confirm CITRON_TOKEN matches it.
A busy engine returns 503 and the submission is retried automatically; repeated failures usually mean the container is down.
API Reference
Authentication API
The Pomelo frontend uses NextAuth for authentication. API routes enforce authentication using the following middleware:
| Middleware | Description |
|---|---|
requireAuth() | Enforces standard authentication for routes. |
isAdmin | Enforces admin-only access for elevated routes. |
Contests API
Endpoints for managing and participating in contests.
| Method | Endpoint | Description |
|---|---|---|
POST | /api/contests/join | Validates a join code and returns a contestId. |
GET | /api/contests/:id | Fetches public contest landing data. |
POST | /api/contests/start | Initializes an attempt session for a user. |
GET | /api/contests/:id/data | Fetches the active questions and test data for an ongoing attempt. |
POST | /api/contests/:id/end | Submits the final test and locks the session. |
GET | /api/contests/:id/leaderboard | Admin-only route to view ranked scores. |
Submissions API
Endpoints for handling code execution and test submissions.
| Method | Endpoint | Description |
|---|---|---|
POST | /api/contests/:id/run | Performs a dry-run code execution against sample test cases. |
POST | /api/submit | The unified submission endpoint for all question types. |