2026-07-07 · 15 min read · Kendra Mazara

Leer en español →

Install Project Achilles in 30 Minutes: Step-by-Step Guide

Install the Achilles platform step by step — local Docker (free) or a DigitalOcean VPS (~$8/mo).

Series: Getting Started with Project Achilles — Part 1 of 6

Difficulty: Beginner 🟢


TL;DR

  • Achilles has two parts: the platform (dashboard + server) and the agent (on every machine you want to validate)
  • This post covers the platform only; we install the agent in QS-02
  • Two options: Docker on your local machine (Option A, free) or a cloud VPS like DigitalOcean (Option B, ~$8/mo)
  • When you finish you will have the dashboard running with sample data
  • Estimated time: 30 min (local) or 40 min (DigitalOcean)

What You Are Going to Install

Achilles has two parts:

PART 1: The Achilles platform
→ The web dashboard you use to view results
→ The database where tests are stored
→ The server the agents connect to

PART 2: The agent (on every machine you want to monitor)
→ A small program (~8 MB)
→ Runs the attack simulations
→ Reports results back to the dashboard

First you install the platform. Then you install the agent on your machines.


Choose Your Platform Option

Option A: Self-hosted with Docker — Free 🐳

Who it's for: You want everything to stay on your infrastructure, or you don't want to pay.

Pros:
✅ Completely free
✅ All data stays on your network
✅ Full control

Cons:
→ You need a dedicated machine (it can be the same one where
   you install the agent if it's a test box)
→ You manage the updates

Minimum server requirements:

Operating system: Linux, macOS, or Windows with Docker Desktop
RAM: 2 GB minimum (4 GB recommended)
Disk: 10 GB free
Docker: version 24 or newer

Do you have Docker installed?

docker --version
# Docker version 24.x.x — ✓ ready
 
docker compose version
# Docker Compose version v2.x.x — ✓ ready

If you don't have Docker:


Option B: Cloud server (e.g. DigitalOcean) — ~$8/mo ☁️

Who it's for: You want the dashboard reachable from anywhere and agents able to connect from outside your local network.

Pros:
✅ Reachable from anywhere — a public URL for you and the agents
✅ Doesn't consume resources on your local machine
✅ Data stays on YOUR server, not a third party's

Cons:
→ Costs ~$8/mo (a basic DigitalOcean droplet or equivalent)
→ You manage the server (updates, backups)
→ Requires the same installation steps as the local option

How to start: Follow Section 1B of this post.

Note: Achilles is open-source; there is no "managed cloud" edition. You install it, you control it. The cloud is simply where you choose to run it.


Section 1A: Install Locally (Docker on your own machine)

(If you chose Option B, DigitalOcean, skip to Section 1B)

Step 1: Download Achilles

git clone https://github.com/projectachilles/ProjectAchilles
cd ProjectAchilles

If you don't have git: download the ZIP from GitHub → "Code" → "Download ZIP", extract it, and enter the folder.

Step 2: Create your Clerk account (free authentication)

Achilles uses Clerk for user login. You need to create a free app:

1. Go to https://clerk.com → "Start building for free"
2. Create an account (it's free)
3. Create a new application:
   Name: "Achilles"
   Sign in options: leave Email enabled (the rest are optional)
   → Enabling Google also works; it adds the
     "Continue with Google" button to the login
   → Password doesn't appear here; you can enable it later in the
     dashboard if you need it (email code works just as well)
4. Click "Create application"
5. In the Clerk dashboard → Configure → API Keys
6. Copy these two values:
   → Publishable key: pk_test_xxxxxxxxxx...
   → Secret key:      sk_test_xxxxxxxxxx...

Step 3: Configure the environment variables

The repo includes two example files (.env.example) that serve as templates. The cp command copies them under the name .env, which is the real file Achilles reads on startup. You need to create one for each part:

# For the server (backend) — this is where the Clerk keys, CORS, etc. go
cp backend/.env.example backend/.env
 
# For the frontend — Docker Compose reads it when building the container
cp .env.example .env

The .env.example files are never modified — they are the template stored in the repo. The .env files are your local copy with your real values, and they are in .gitignore so they never get pushed to GitHub.

Open backend/.env with nano backend/.env and change only these two values:

# ── Clerk (required) ───────────────────────────
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxx
CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxx

CORS_ORIGIN — you don't need to change it if you use Docker. Nginx serves the frontend on port 80 and proxies to the backend internally; the browser never calls port 3000 directly and CORS never kicks in. It only matters if you run Achilles outside Docker (development mode). ENCRYPTION_SECRET and AGENT_SERVER_URL — leave them as they are. We adjust AGENT_SERVER_URL in QS-02 when we install the agent.

Open .env with nano .env and add your Clerk publishable key at the end of the file — the root .env.example doesn't include this field, you have to add it manually:

# ============ Clerk (Frontend) ============
CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxx

Why two files? backend/.env is read directly by the server. The root .env is read by Docker Compose on startup and passed to the frontend container. They are two separate configuration paths.

Step 4: Start everything

docker compose --profile elasticsearch up -d

Why --profile elasticsearch? Achilles stores test results in Elasticsearch — the Analytics module uses it to compute the Defense Score, the MITRE ATT&CK heatmap, and the trends. Without that flag only the backend and frontend start, and Analytics has no data. The profile also loads 1,000 sample results automatically.

Wait 1-2 minutes while the containers start. Verify everything is running:

docker compose ps
 
# You should see:
# achilles-backend    Up   0.0.0.0:3000->3000/tcp
# achilles-frontend   Up   0.0.0.0:80->80/tcp
# elasticsearch       Up   0.0.0.0:9200->9200/tcp

Step 5: Open the dashboard

Open your browser at: http://localhost

You will see the Achilles landing page. Click SIGN IN (top right or in the middle of the page).

Achilles — landing page

You will see Clerk's login modal. You have two options:

  • With Google: click "Continue with Google" — Clerk handles the OAuth automatically. This button appears if you left Google enabled in Step 2.
  • With email: click Sign up (at the very bottom), fill in your name, email, and password, and click Continue.

Clerk — Sign in

If you go with email, clicking Sign up shows the account creation form:

Clerk — Create account

Step 6: Assign your administrator role

After creating your account you will see the dashboard, but the Endpoints module (where you enroll and manage agents) will be hidden:

Dashboard without Endpoints

This is normal — Achilles uses a role system and the first account gets no role assigned automatically. To enable all modules, assign the admin role to your user in Clerk:

  1. Go to dashboard.clerk.com → your application → Users
  2. Click your user
  3. Find the "Public metadata" section and enter:
    {"role": "admin"}
  4. Save and reload Achilles

You will see the Endpoints module appear in the sidebar with Dashboard, Agents, and Tasks:

Dashboard with Endpoints visible

Why do you have to do this manually? Achilles doesn't assign the admin role to the first user automatically, to prevent anyone who signs up from getting full access. You control who gets which access level from Clerk.

Step 7: Configure the Clerk Session Token

This step is required — without it, the role you just assigned never reaches the dashboard and Achilles keeps treating you as a user with no permissions.

Clerk issues a JWT (session token) the frontend reads to know which role you have. By default that token does not include the publicMetadata where you stored {"role": "admin"}. You have to add it manually:

  1. Go to dashboard.clerk.com → your application → Configure → Sessions
  2. Find the "Customize session token" section
  3. In the Claims editor (it shows {}), replace the content with:
    {
      "metadata": "{{user.public_metadata}}"
    }
  4. Click Save
  5. Sign out of Achilles and sign back in

You will see the Endpoints module appear in the sidebar. If your session was already open when you assigned the role, signing out and back in forces Clerk to issue a new token with the role included.

Why {{user.public_metadata}}? It's Clerk's syntax for injecting the user's publicMetadata field into the JWT. The Achilles frontend reads user.publicMetadata.role from the token — if that field isn't in the token, the role doesn't exist as far as the dashboard is concerned, even though you saved it in Clerk.

Step 8: Verify Elasticsearch

Go to Settings → Integrations. You should see Analytics (Elasticsearch) in Connected state — Docker connects Elasticsearch automatically on startup, you don't need to configure anything.

Settings — Integrations

If for some reason it shows "Not configured", expand the card and enter:

Elasticsearch URL: http://elasticsearch:9200

Save and it should connect immediately.

Analytics — Dashboard


Section 1B: Install on DigitalOcean (cloud VPS)

(If you chose Option A, local Docker, you are already done with Section 1A; skip to Section 2)

Step 1: Create the droplet on DigitalOcean

  1. Go to https://digitalocean.com → "Sign Up" (or log in)

  2. Click "Create" → "Droplets"

  3. Choose the configuration:

    • Region: the closest one to your machines (e.g. NYC, AMS, FRA)
    • Image: Ubuntu 22.04 LTS x64
    • Plan: Basic → Premium Intel → $8/mo (1 vCPU, 1 GB RAM, 35 GB NVMe SSD)
  4. Authentication with an SSH key (recommended):

    In your local terminal, generate a key if you don't have one:

    ssh-keygen -t ed25519
    # Press Enter through every prompt to accept the defaults

    Then copy your public key:

    cat ~/.ssh/id_ed25519.pub

    In DigitalOcean click "Add SSH Key", paste the content, give it a name (e.g. my-laptop), and click "Add SSH Key". Check the key's checkbox to select it.

    If you prefer not to use SSH, select the Password tab and set a strong password.

  5. Give the droplet a name (e.g. achilles-server) and click "Create Droplet"

  6. Wait ~1 minute — DigitalOcean will show you the server's public IP (e.g. <public-IP>)

Step 2: Connect to the server

# From your local terminal (Mac/Linux) — use your public IP
ssh root@<public-IP>
 
# Windows: use PuTTY or Windows Terminal with:
# ssh root@<public-IP>

Step 3: Install Docker, Git, and prepare the system

# Update the system
apt update && apt upgrade -y
 
# Install Docker
curl -fsSL https://get.docker.com | sh
 
# Verify Docker is running
docker --version
docker compose version
 
# Install Git
apt install -y git

Before continuing, add swap:

fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

Why? Docker builds three images in parallel (frontend, backend, wiki). On a 1 GB server that process exhausts the RAM and the system kills the build midway. Swap works as backup RAM on disk — not as fast, but it keeps the build from failing.

Alternative: If you'd rather not deal with this, use the $12/mo plan (2 GB RAM) — Basic → Premium Intel → $12/mo. The build runs without issues and the dashboard responds faster with multiple active agents.

Step 4: Download Achilles

git clone https://github.com/projectachilles/ProjectAchilles
cd ProjectAchilles

Step 5: Expose the ports to the outside

For security, the docker-compose.yml binds the ports to 127.0.0.1 (localhost) only. That's fine on a local machine where you are the only user, but on a VPS it means nobody can reach it from outside — not you from your browser, and not the agents trying to connect to the backend.

Edit the file to open the ports on all interfaces:

sed -i 's/127.0.0.1:3000/0.0.0.0:3000/g' docker-compose.yml
sed -i 's/127.0.0.1:80/0.0.0.0:80/g' docker-compose.yml

These two commands replace the localhost binding with 0.0.0.0 (all network interfaces), which is what you need for the server to be reachable from the internet.

Step 6: Create your Clerk account (free authentication)

Achilles uses Clerk for user login. It's free and requires no credit card.

  1. Go to https://clerk.com"Start building for free" and create an account
  2. Create a new application:
    • Name: Achilles
    • Sign in options: leave Email enabled — it's enough to get started
    • If you enable Google, the "Continue with Google" button will appear on the login (optional)
  3. Click "Create application"
  4. Go to Configure → API Keys and copy these two values:
    • Publishable key: starts with pk_test_... — goes in the frontend and the backend
    • Secret key: starts with sk_test_... — backend only, never expose it

Step 7: Configure the environment variables

First create the .env files from the examples and generate the security secrets:

cp backend/.env.example backend/.env
 
openssl rand -base64 32   # for SESSION_SECRET (save the result)
openssl rand -base64 32   # for ENCRYPTION_SECRET (save the result)

Then open the backend .env:

cd backend
nano .env

Use Ctrl+W to search for each field. Edit only these 6 values:

VariableValue
CLERK_PUBLISHABLE_KEYyour pk_test_... from Clerk
CLERK_SECRET_KEYyour sk_test_... from Clerk
SESSION_SECRETresult of the first openssl rand
ENCRYPTION_SECRETresult of the second openssl rand (uncomment the line by removing the #)
CORS_ORIGINhttp://<public-IP> — lets the frontend talk to the backend from outside
AGENT_SERVER_URLhttp://<public-IP>:3000 — the URL agents will use to connect

Why CORS_ORIGIN and AGENT_SERVER_URL with the public IP? Locally everything runs on the same machine and the browser reaches it via localhost. On DigitalOcean you access it from another machine — the backend needs to know which origin to allow (CORS) and the agents need to know which URL to connect to.

Save with Ctrl+X → Y → Enter.

Now configure the root .env (from ~/ProjectAchilles):

cd ..
cp .env.example .env
nano .env

Go to the end of the file and add this line:

CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxx

Save with Ctrl+X → Y → Enter.

Why this file, and why at the end? The root .env is read by Docker Compose on startup and passed to the frontend container as an environment variable. The frontend (React) needs Clerk's publishable key to show the login modal — without it the page loads but you can't authenticate.

The root .env.example doesn't include this field because in local installs the frontend usually runs outside Docker (with npm run dev) and reads its variables from somewhere else. In Docker you have to add it manually. It's the only line you need to add.

Step 8: Open the ports in the DigitalOcean firewall

In the DigitalOcean panel → your droplet → "Networking" → "Firewalls"
→ Create Firewall → add these Inbound rules:

  Type    Port      Source
  ─────────────────────────────────────
  HTTP    80        All IPv4, All IPv6
  Custom  3000      All IPv4, All IPv6

→ Apply the firewall to the "achilles-server" droplet

If you prefer using ufw from the server:

ufw allow 22/tcp    # SSH — add this FIRST or you lock yourself out
ufw allow 80/tcp
ufw allow 3000/tcp
ufw enable

Step 9: Tune Elasticsearch memory

Elasticsearch ships configured to use up to 1 GB of heap — too much for a 1 GB server. Before starting, lower that limit:

sed -i 's/ES_JAVA_OPTS=-Xms512m -Xmx1024m/ES_JAVA_OPTS=-Xms256m -Xmx256m/' docker-compose.yml

Why? Elasticsearch is a Java database designed for servers with plenty of RAM. On a 1 GB VPS it competes with the backend, the frontend, and the operating system. With 256 MB of heap it works fine for normal use — if you have many agents or heavy queries, consider the $12/mo plan (2 GB).

Step 10: Start everything

docker compose --profile elasticsearch up -d backend frontend elasticsearch es-seed

Why not plain docker compose up? The wiki service (documentation) consumes too much RAM during the build on a 1 GB server and kills the process. It isn't needed to use Achilles — we exclude it by naming the services explicitly.

The first run takes 15-20 minutes because it builds the images from scratch. Subsequent starts take seconds thanks to the cache.

When it finishes, verify everything is running:

docker compose ps

You should see backend, frontend, and elasticsearch in Up or Healthy state.

Step 11: Open the dashboard

From your browser (on any computer): http://<public-IP>

You will see the Achilles landing page. Click SIGN IN to continue.

Achilles — landing page from DigitalOcean

Login and account creation work the same as in the local install — follow the same steps from Step 5 of Section 1A.

Step 12: Assign your administrator role

Just like in the local install, after creating your account the Endpoints module will be hidden:

Dashboard without Endpoints

Follow the same steps from Step 6 of Section 1A ({"role": "admin"} in the user's Public metadata in Clerk).

Once the role is assigned you will see the full sidebar with Tests, Analytics, and Endpoints:

Dashboard with Endpoints visible

Step 13: Configure the Clerk Session Token

Just like in the local install (Step 7 of Section 1A), you have to add the publicMetadata to Clerk's JWT. Without this step the role you assigned in Clerk never reaches the dashboard:

  1. Go to dashboard.clerk.com → your application → Configure → Sessions
  2. In the "Customize session token" section, in the Claims editor, replace {} with:
    {
      "metadata": "{{user.public_metadata}}"
    }
  3. Click Save
  4. Sign out of Achilles and sign back in so the new token gets issued

Step 14: Verify Elasticsearch

Go to Settings → Integrations. You should see Analytics in Connected state — Docker connects Elasticsearch automatically, you don't need to configure anything.

Settings — Integrations

If it shows "Not configured", enter:

Elasticsearch URL: http://elasticsearch:9200

Analytics — Dashboard


Summary: Installing the Platform

OPTION A — Local (free):
  1. Clone repo + configure .env              (10 min)
  2. docker compose up                        (5 min)
  3. Create account + assign admin role       (5 min)
  4. Configure the Session Token in Clerk     (2 min)
  5. Verify Elasticsearch                     (2 min)
  Total: ~25 minutes

OPTION B — DigitalOcean (~$8/mo):
  1. Create Ubuntu droplet                    (5 min)
  2. Install Docker, Git, and swap            (5 min)
  3. Clone repo + edit docker-compose.yml     (3 min)
  4. Create Clerk account                     (5 min)
  5. Configure .env with the public IP        (5 min)
  6. Open firewall ports (SSH first)          (2 min)
  7. docker compose up                        (20 min first time)
  8. Assign admin role + Session Token        (5 min)
  9. Verify Elasticsearch                     (2 min)
  Total: ~40 minutes (+ 20 min of build in the background)

Key Takeaways

✅ Two options: local with Docker (free) or a VPS like DigitalOcean (~$8/mo) ✅ The platform includes the dashboard, backend, and Elasticsearch, all with a single command ✅ Sample data loads automatically when starting with --profile elasticsearch ✅ When you finish this post you have the dashboard running; the agent comes in QS-02 ✅ The agent and the first test are covered in QS-02 and QS-04


Next Post

QS-02: "Connect Your First Machine"

Install the agent step by step on Windows, Linux, or macOS and make your first machine appear on the dashboard.