Ends in
00
days
00
hrs
00
mins
00
secs
ENROLL NOW

🚀 Extended! 25% OFF All Practice Exams & Video Courses, $2.99 eBooks, Savings on PlayCloud and CodeQuest!

Self-Hosting Judge0: A Step-by-Step Guide Using Amazon EC2, Lambda, and S3

Home » Others » Self-Hosting Judge0: A Step-by-Step Guide Using Amazon EC2, Lambda, and S3

Self-Hosting Judge0: A Step-by-Step Guide Using Amazon EC2, Lambda, and S3

Judge0 is a powerful, open-source code execution system widely used by online coding platforms to run programs in secure environments. However, while it is often accessed as a third-party service, self-hosting Judge0 on AWS significantly grants you complete control over configuration and security. In fact, this level of ownership is essential for handling sensitive data or customizing language support. Moreover, self-hosting enables greater scalability, thereby allowing you to adapt the system as your requirements evolve. Consequently, if you are looking for a robust solution to enhance your coding platform, this guide is the ideal choice.

This step-by-step guide will walk you through deploying your own Judge0 backend on an AWS EC2 instance and connecting it to a live frontend application. Perfect for beginners and cloud enthusiasts, this tutorial moves beyond theory to offer hands-on experience with real-world infrastructure. By the end, you will have a fully functional, self-hosted code execution engine running entirely on your own terms.

Pre-requisites

Part 1: Self-Hosted Judge0 API Using EC2 Instance

This section of the tutorial walks you through setting up your own instance of the Judge0 API using an Amazon EC2 instance. We will cover everything from launching the server to fixing common cgroup issues on modern Linux distributions.

Prerequisites

  • An AWS Account
  • Terminal (Mac/Linux) or Git Bash/PowerShell (Windows)
  • Git installed locally

Step 1: Fork and Prepare the Configuration

To make this tutorial easier to follow, we have prepared a specific repository for you. 

1. Fork the Repository 

Fork the repository: https://github.com/Klaire-Nut/self-hosted-judge0

2. Clone Your Fork

  • Once you have forked the repo, clone your version to your local machine. (Replace <YOUR_USERNAME> with your actual GitHub username):
git clone https://github.com/<YOUR_USERNAME>/self-hosted-judge0
cd self-hosted-judge0/backend
    • This is a monorepo containing both frontend and backend code. For this section, we are strictly focusing on the backend folder, which contains the configured Judge0 files. In the screenshots below, you might see a folder named judge0—this is simply the renamed backend folder. It was renamed here for clearer file organization, so don’t be confused!

3. Verify Configurations

  • We have pre-configured the judge0.conf file for you since the default authentication settings are often empty.
    • Authentication Token: tutorialsdojoSpaghetti2025AuthToken
    • Why? We have specifically set the auth token to ensure consistency for this project and make the later integration steps easier. However, feel free to change this token to your own secure string if you prefer!

4. Modify Port Configuration

  • Ensure the docker-compose.yml maps port 80 to the internal port.
    • Check: ports: - "80:2358"

docker-compose.yml configuration

Step 2: Launch an EC2 Instance

  1. Navigate to EC2 Dashboard and click Launch Instance.
  2. Name and Tags:
    • Set the name to self-hosted-judge (you can rename this whatever you want)
  3. AMI:
    • Select Amazon Linux 2023launch an EC2 instance
  4. Instance Type:
    • Choose t3.medium 
  5. Key Pair:
    • Create a new keypair and assign it to this instance. Download the .pem file and keep it safe
      • When you download the .pem file, note exactly where it is saved (usually your Downloads folder).
    • key pair and instance type
  6. Network Settings:
    • Auto-assign Public IP: Enable.
    • Security Group: Create a new security group. Ensure you add an Inbound Rule to allow HTTP (Port 80) traffic from anywhere (0.0.0.0/0) so the API is accessible. SSH (Port 22) should also be open.
  7. Configure Storage:
    • Set root volume to 40 GiB. This ensures sufficient space for all dependencies and avoids the need to resize the volume later.
  8. Tutorials dojo strip
  9. Click Launch Instance.

network settings and storage

Step 3: Configure Elastic IP

To ensure your API endpoint doesn’t change if the server restarts, assign a static IP.

  1. In the EC2 sidebar, go to Network & Security > Elastic IPs.
  2. Click Allocate Elastic IP address and confirm.
  3. Select the newly allocated IP and click Associate Elastic IP address.
  4. Instance: Select your running self-hosted-judge instance.
  5. Click Associate.

allocate elastic ip address

Step 4: Connect and Install Dependencies

Now, connect to your server to install Docker and Docker Compose.

  • Set Key Permissions (on your local machine): 
# Syntax: chmod 400 /path/to/your/downloaded/key.pem
chmod 400 JUDGE0-KEY.pem
  • Connect via SSH:
# Syntax: ssh -i “path/to/key” user@ip-address
ssh -i “JUDGED-KEY.pem” ec2-user@<YOUR_ELASTIC_IP>
  • Install Docker:
sudo yum update -y
sudo yum install docker -y
sudo systemctl start docker
sudo systemctl enable docker
  • Add User to Docker Group (allows running docker without sudo):
sudo usermod -a -G docker ec2-user
    • Important: Log out (exit) and log back in for this to take effect.
  • Install Docker Compose:
sudo curl -L “https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
  • To verify if Docker and Docker Compose were installed, run docker-compose -v and docker –version.

Step 5: Fix Cgroup Error (Critical Step)

Modern Linux systems (like Amazon Linux 2023) use cgroup v2 by default. Judge0 relies on cgroup v1. If you skip this, you will see errors like No such file or directory @ rb_sysopen or status 13 (Internal Error).

  • Edit GRUB Configuration:
sudo nano /etc/default/grub
  • Modify the Kernel Line:
    • Find the line starting with GRUB_CMDLINE_LINUX_DEFAULT. Append the following parameters inside the quotes:
systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller=1

modify kernel line

  • Update GRUB:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
  • Reboot the Instance:
sudo reboot

Step 6: Deploy Judge0

1. Transfer Files (SCP)

  • Run this command from your local terminal (not inside the EC2 connection):
    • Note: We are copying the local backend folder, but naming it judge0 on the server to match standard conventions.
# Syntax: scp -i “path/to/key” -r “path/to/local/backend_folder” user@ip:destination
scp -i “/Users/username/Downloads/JUDGED-KEY.pem” -r /path/to/your/documents/self-hosted-judge0/backend ec2-user@:/home/ec2-user/judge0

2. Start the Services

  • SSH back into the server (using the command from Step 4), then run:
cd judge0
docker-compose up -d

3. Verify Deployment

  • SSH back into the server (using the command from Step 4 then run):
docker ps
    • You should see judge0/judge, postgres, and redis with status Up.

Success! Your self-hosted Judge0 API is now running at http://<YOUR_ELASTIC_IP>/

 

Part 2: Setting Up the Frontend Application

In this section, we will run the frontend user interface locally. Since we are building a secure architecture where the frontend talks to AWS Lambda (which we will build in Part 3) instead of directly to the EC2 instance, we will set up the configuration variables now but leave them empty for the next step.

Step 1: Navigate to the Frontend Folder

  • Locate the frontend folder in your forked repository. It contains these three core files:
    • index.html
    • style.css
    • script.js
cd /path/to/frontend

Step 2: Configure the API URL in script.js

  • We need to prepare the code to accept the AWS Lambda URL later.
    1. Open script.js in your code editor.
    2. Locate the line where the API URL is defined (usually near the top).
    3. Ensure the variable is set to an empty string. We are purposefully leaving this blank because we will generate this URL in the next part of the tutorial.

script.js lambda url

Step 3: Run the Frontend

Because this is a vanilla HTML/JS project, you can open it directly in your browser.

  • Mac: open index.html
  • Windows: Double-click index.html in your file explorer.
  • Linux: xdg-open index.html
  • VSCode: Navigate to index.html and press Go Live.

Your interface should load immediately.

Step 4: Verify the Interface

  • Once the file opens in your browser, check the following:
    • Does the Code Editor load correctly?
    • Can you type inside the editor?
    • Do the dropdown menus (Language Select) appear?

Step 5: A Note on the “Run Code” Button

At this stage, do not worry if clicking “Run Code” does nothing or shows an error in the console.

  • Current Status: The frontend is loaded, but it has “nowhere to go” yet because our LAMBDA_URL is empty.
  • In Part 3, we will create the AWS Lambda function to act as the bridge between this frontend and your Judge0 EC2 instance. Once that is done, we will paste the URL here, and the magic will happen!

Frontend setup is complete! Let’s move on to the AWS Lambda configuration.

 

Part 3: Linking the Frontend to Your Self-Hosted API

Now that our Judge0 API is running on EC2, we need to connect it to our frontend. We will use AWS Lambda as a bridge. This ensures our frontend (hosted securely on HTTPS) can communicate with our EC2 instance (running on HTTP) without browser security errors.

Step 1: Create the Lambda Function

  • Navigate to the AWS Lambda Console.
  • Click Create Function.
    • Click “Author from scratch” 
    • Name: self-hosted-judge0 (rename this to whatever you like)
    • Runtime: Python 3.12
    • Architecture: x86_64
  • Free AWS Courses
  • Click Create function.

Configuring new Lambda Function (1)

Configuring new Lambda Function (2)

    • After creating a function, write the backend logic inside lambda_function.py. Then, click the “Deploy” button to save changes. 
    • Important Note: You must add a Layer containing the requests library for this code to work, as standard Python Lambda does not include it. (You can see the request file zip file in the repository provided)
import os
import requests
import json
import base64

def lambda_handler(event, context):
    print("DEBUG: Lambda invoked with event:", event)

    # Self-hosted Judge0 configuration
    JUDGE0_API_URL = os.environ.get("JUDGE0_API_URL", "http://localhost:2358")
    JUDGE0_AUTH_TOKEN = os.environ.get("JUDGE0_AUTH_TOKEN")

    if not JUDGE0_AUTH_TOKEN:
        raise Exception("Missing JUDGE0_AUTH_TOKEN in environment variables")

    headers = {
        "Content-Type": "application/json",
        "td-auth-token": JUDGE0_AUTH_TOKEN,
    }

    # Parse event body
    body = event.get("body")
    if body and isinstance(body, str):
        try:
            body = json.loads(body)
        except json.JSONDecodeError:
            body = {}
    else:
        body = {}

    # Get code submission details from event
    # 'source_code' is not base64 encoded when sent, so it's already "decoded".
    source_code = body.get("source_code", "print('Hello, World!')")
    language_id = body.get("language_id", 71)
    stdin = body.get("stdin", "")

    print("STD INPUT", stdin)
    print("SOURCE CODE", source_code)

    # Check if stdin is empty and handle accordingly
    # Store the original stdin to print it later
    original_stdin = stdin
    if stdin == "":
        stdin = None

    submission_data = {
        "language_id": language_id,
        "source_code": source_code,
        "stdin": stdin
    }

    print("DEBUG: Submitting code to Judge0:", submission_data)

    # Submit code to Judge0 API with wait=true (Judge0 handles polling internally)
    submit_response = requests.post(
        f"{JUDGE0_API_URL}/submissions?wait=true",  
        headers=headers,
        json=submission_data,
        timeout=30,
    )

    print("DEBUG: Submission response:", submit_response.status_code, submit_response.text)

    if submit_response.status_code != 201:
        return {
            "statusCode": submit_response.status_code,
            "body": json.dumps({"error": submit_response.text})
        }

    result = submit_response.json()
    print("DEBUG: Execution finished with result:", result)

    # Decode the base64-encoded output for the final print statement
    stdout = result.get("stdout", "")
    decoded_stdout = ""
    if stdout:
        try:
            decoded_stdout = base64.b64decode(stdout).decode('utf-8')
        except Exception as e:
            print(f"Warning: Could not decode stdout: {e}")
            decoded_stdout = f"!!! Base64 Decoding Failed: {stdout} !!!"

    stderr = result.get("stderr", "")
    compile_output = result.get("compile_output", "")

    if stderr:
        decoded_stderr = base64.b64decode(stderr).decode('utf-8')
        print("DEBUG: stderr decoded:", decoded_stderr)
    if compile_output:
        decoded_compile_output = base64.b64decode(compile_output).decode('utf-8')
        print("DEBUG: compile_output decoded:", decoded_compile_output)

    # --- THE REQUESTED SINGLE PRINT STATEMENT ---
    print(f"\n--- Execution Summary ---\n"
          f"Source Code:\n{'-'*20}\n{source_code}\n"
          f"Standard Input:\n{'-'*20}\n{original_stdin}\n"
          f"Standard Output (Decoded):\n{'-'*20}\n{decoded_stdout}\n"
          f"-------------------------")
    # -------------------------------------------

    return {
        "statusCode": 200,
        "body": json.dumps(result)
    }

Step 2: Add Environment Variables

We need to tell Lambda where your EC2 instance lives.

  1. Go to Configuration > Environment variables.

  2. Click Edit > Add environment variable.

  3. Add the following key-value pairs:

    • Key: JUDGE0_API_URL Value: http://<YOUR_EC2_ELASTIC_IP> (e.g., http://54.123.45.67)

    • Key: AUTH_TOKEN Value: tutorialsdojoSpaghetti2025AuthToken (The token configured in Part 1).

  4. Click Save.

Adding Environment Variables to Lambda Function

Step 3: Adjust General Configuration

  1. Go to Configuration > General configuration > Edit.

  2. Memory: 512MB, Ephemeral Storage: 512MB
  3. Timeout: Increase to 30 seconds.

    • Reason: Standard API calls are fast, but this provides a buffer.

  4. Click Save.

Lamda Function General Configuration

Step 4: Enable Function URL & CORS

  1. Go to Configuration > Function URL.

  2. Click Create function URL.

  3. Auth type: NONE (Public).

  4. Click Configure cross-origin resource sharing (CORS) and set:

    • Allow origin: *

    • Allow headers & Expose headers: content-type, access-control-allow-origin, access-control-allow-methods

    • Allow methods: *

  5. Click Save.

Configure Function URL (1)

Configure Function URL (2)

 

Part 4: Hosting the Static Website (S3 & CloudFront)

Finally, we deploy the frontend files so the world can access your Code Runner.

1. Prepare Frontend Code

  1. Open your local script.js file.
  2. Find the variable for the API endpoint (e.g., LAMBDA_URL or API_URL).
  3. Paste the Function URL.
    • Example: const API_URL = "https://<your-id>.lambda-url.us-east-1.on.aws/";
  4. Save the file.

2. Create S3 Bucket

  1. Go to Amazon S3 > Create bucket.

  2. Bucket name: judge0-frontend-demo-<your-name> (namemust be unique).

  3. Block Public Access: Uncheck “Block all public access” and acknowledge the warning.

  4. Click Create bucket.

3. Upload Files

  1. Open the newly created bucket.

  2. Upload index.html, style.css, and your updated script.js.

S3 Bucket

4. Enable Static Hosting

  1. Go to the Properties tab.

  2. Scroll down to Static website hosting.

  3. Click Edit > Enable.

  4. Index document: index.html.

  5. Click Save changes.

5. Configure CloudFront 

  1. Go to CloudFront > Create distribution.

  2. Origin domain: Click the field and select your S3 bucket’s Website Endpoint.

    • Tip: Use the endpoint format bucket-name.s3-website-us-east-1.amazonaws.com, NOT the S3 bucket ARN.

  • Invalidate CloudFront Cache:
    • Perform a cache invalidation after deployment to ensure the latest files are served.
  • Verify Distribution:
    • Confirm that the CloudFront Domain Name is live and accessible.

6. Final Test

  1. Copy the Distribution Domain Name (e.g., d123.cloudfront.net) and open it in your browser.

  2. Run a code snippet.

Self-Hosted Judge0 live

 

Congratulations on successfully building your own production-ready self-hosted Judge0 from scratch!

You have journeyed through the core of cloud computing: from configuring a raw Linux server on EC2 and debugging low-level cgroups to bridging secure connections with AWS Lambda, and finally deploying a global frontend using S3 and CloudFront. This article wasn’t just a tutorial on self-hosting; it was a hands-on masterclass in connecting the dots between infrastructure, security, and application logic. We hope this project gives you the confidence to dive deeper into the AWS ecosystem and continue building excellent, scalable solutions. Well done, and happy coding!

 

🚀 Extended! 25% OFF All Practice Exams & Video Courses, $2.99 eBooks, Savings on PlayCloud and CodeQuest!

Tutorials Dojo portal

Learn AWS with our PlayCloud Hands-On Labs

🧑‍💻 50% OFF – CodeQuest Coding Labs

$2.99 AWS and Azure Exam Study Guide eBooks

tutorials dojo study guide eBook

New AWS Generative AI Developer Professional Course AIP-C01

AIP-C01 Exam Guide AIP-C01 examtopics AWS Certified Generative AI Developer Professional Exam Domains AIP-C01

Learn GCP By Doing! Try Our GCP PlayCloud

Learn Azure with our Azure PlayCloud

FREE AI and AWS Digital Courses

FREE AWS, Azure, GCP Practice Test Samplers

Subscribe to our YouTube Channel

Tutorials Dojo YouTube Channel

Follow Us On Linkedin

Written by: Klaire Napolitano

Klaire is a BS Computer Science student from the University of the Philippines Mindanao who thrives at the intersection of technology and creativity. She plays an active role in campus organizations, taking part in initiatives that highlight design, collaboration, and digital innovation. With a strong interest in creative production and tech-driven storytelling, she enjoys bringing ideas to life through visual and interactive projects. Klaire aims to keep growing as both a developer and creative, using her skills to inspire and connect people through meaningful work.

AWS, Azure, and GCP Certifications are consistently among the top-paying IT certifications in the world, considering that most companies have now shifted to the cloud. Earn over $150,000 per year with an AWS, Azure, or GCP certification!

Follow us on LinkedIn, YouTube, Facebook, or join our Slack study group. More importantly, answer as many practice exams as you can to help increase your chances of passing your certification exams on your first try!

View Our AWS, Azure, and GCP Exam Reviewers Check out our FREE courses

Our Community

~98%
passing rate
Around 95-98% of our students pass the AWS Certification exams after training with our courses.
200k+
students
Over 200k enrollees choose Tutorials Dojo in preparing for their AWS Certification exams.
~4.8
ratings
Our courses are highly rated by our enrollees from all over the world.

What our students say about us?