Skip to content

Latest commit

 

History

History
351 lines (247 loc) · 17.9 KB

File metadata and controls

351 lines (247 loc) · 17.9 KB

Deploying ProjectAchilles to Render.com

This guide covers deploying ProjectAchilles to Render using the included Blueprint (render.yaml) or manual setup, with Elastic Cloud for analytics.

Architecture

Render runs two web services from this monorepo:

Service Root Directory Dockerfile Public Domain
achilles-backend backend/ backend/Dockerfile Yes (agents connect here)
achilles-frontend frontend/ frontend/Dockerfile Yes (users visit this)

The frontend calls the backend directly via CORS (using VITE_API_URL). Elasticsearch is handled externally by Elastic Cloud.

Note: Render's private network DNS is only available on Standard plan and above. On Starter plan, services cannot resolve each other by name, so nginx proxy mode (BACKEND_HOST) does not work. The frontend uses VITE_API_URL to call the backend's public URL directly instead.

Prerequisites

  • Render account (render.com) — Starter plan or above (required for persistent disks)
  • GitHub repo with this project pushed
  • Clerk application keys (dashboard.clerk.com) — create a separate Clerk app for Render
  • Elastic Cloud deployment (cloud.elastic.co)
  • GitHub Personal Access Token (if using a private test repo)

Option A: Blueprint Deploy (Recommended)

The fastest path — Render reads render.yaml and creates both services automatically.

  1. Go to render.com/deploy and connect your GitHub repo

  2. Render detects render.yaml and shows the Blueprint preview

  3. Review the services, then click Apply

  4. After creation, go to each service's Environment tab and set the sync: false variables:

    Backend (achilles-backend):

    CLERK_PUBLISHABLE_KEY=pk_live_...
    CLERK_SECRET_KEY=sk_live_...
    CORS_ORIGIN=https://<your-frontend>.onrender.com
    AGENT_SERVER_URL=https://<your-backend>.onrender.com
    TESTS_REPO_URL=https://github.com/your-org/f0_library.git
    AGENT_REPO_URL=https://github.com/your-org/ProjectAchilles.git
    GITHUB_TOKEN=ghp_...
    ELASTICSEARCH_CLOUD_ID=<from Elastic Cloud console>
    ELASTICSEARCH_API_KEY=<from Elastic Cloud console>
    

    Frontend (achilles-frontend):

    CLERK_PUBLISHABLE_KEY=pk_live_...
    VITE_API_URL=https://<your-backend>.onrender.com
    

    Important: CORS_ORIGIN and AGENT_SERVER_URL must be full URLs with the https:// scheme. VITE_API_URL tells the frontend where to send API calls (bypassing nginx proxy, which requires private network DNS not available on Starter plan).

  5. Trigger a manual deploy on both services (or push a commit)

SESSION_SECRET and ENCRYPTION_SECRET are auto-generated by Render during Blueprint deploy. Do not override them unless you're migrating from another deployment.

Option B: Manual Setup

Step 1: Create the Backend Service

  1. In Render Dashboard, click NewWeb Service

  2. Connect your GitHub repo

  3. Configure:

    • Name: achilles-backend
    • Root Directory: backend
    • Runtime: Docker
    • Plan: Starter ($7/mo — required for disk)
  4. Add a Disk (Settings → Disks):

    Mount Path Size Purpose
    /root/.projectachilles 1 GB SQLite database, certificates, encrypted settings
  5. Set environment variables (Environment tab):

    Variable Value Notes
    NODE_ENV production
    PORT 3000
    CLERK_PUBLISHABLE_KEY pk_live_... From Clerk dashboard
    CLERK_SECRET_KEY sk_live_... From Clerk dashboard
    SESSION_SECRET <openssl rand -base64 32> Generate a random secret
    ENCRYPTION_SECRET <openssl rand -base64 32> Required — see note below
    CLI_AUTH_SECRET <openssl rand -base64 32> Required for CLI login (achilles login)
    CORS_ORIGIN https://achilles-frontend.onrender.com Your frontend's Render URL
    AGENT_SERVER_URL https://achilles-backend.onrender.com Your backend's Render URL
    TESTS_REPO_URL https://github.com/your-org/f0_library.git Test library repo
    TESTS_REPO_BRANCH main
    AGENT_REPO_URL https://github.com/your-org/ProjectAchilles.git Agent source for builds
    AGENT_REPO_BRANCH main
    GITHUB_TOKEN ghp_... PAT with repo scope
    ELASTICSEARCH_CLOUD_ID <from Elastic Cloud>
    ELASTICSEARCH_API_KEY <from Elastic Cloud> See permissions below

    Elasticsearch API Key Permissions: Create the key in Kibana (Stack Management → API Keys) with these role descriptors:

    { "achilles_role": { "cluster": ["monitor"], "indices": [{ "names": ["achilles-*", "archived-*"], "privileges": ["manage", "read", "write"], "allow_restricted_indices": false }] } }

    ENCRYPTION_SECRET is required on Render. Without it, the backend derives a key from the container's hostname, which changes across deploys and corrupts encrypted settings.

Step 2: Create the Frontend Service

  1. Click NewWeb Service

  2. Connect the same GitHub repo

  3. Configure:

    • Name: achilles-frontend
    • Root Directory: frontend
    • Runtime: Docker
    • Plan: Starter ($7/mo)
  4. Set environment variables:

    Variable Value Notes
    CLERK_PUBLISHABLE_KEY pk_live_... Same publishable key as backend
    VITE_API_URL https://achilles-backend.onrender.com Backend's public URL (direct CORS)

Step 3: Verify

  1. Wait for both services to build and deploy (first build takes 3-5 minutes)
  2. Visit your frontend URL — you should see the Clerk login page
  3. Check backend health: curl https://achilles-backend.onrender.com/api/health
  4. After logging in, go to Analytics → Setup and verify the Elastic Cloud connection

Inter-Service Networking

On Starter plan, the frontend calls the backend directly via CORS using the VITE_API_URL environment variable (set to the backend's public https://*.onrender.com URL). The docker-entrypoint.sh script injects this as window.__env__.VITE_API_URL at container start, and the axios client reads it at runtime.

On Standard plan and above, you can optionally use Render's private network for nginx proxy mode instead. Set BACKEND_HOST to the backend's service name and omit VITE_API_URL — nginx will proxy /api/* requests over the private network. This avoids exposing the backend publicly, but requires the higher-tier plan.

Plan Frontend → Backend Env Var Backend Exposure
Starter Direct CORS VITE_API_URL=https://<backend>.onrender.com Public
Standard+ nginx proxy (private network) BACKEND_HOST=<backend-service-name> Can be private

Persistent Disk

The backend uses a 1 GB persistent disk at /root/.projectachilles:

Path Purpose
agents.db SQLite database (agents, tokens, tasks, schedules)
analytics.json Encrypted Elasticsearch connection settings
tests.json Test repository configuration
certs/ Code signing certificates (max 5, subdirectory per cert)
binaries/ Built agent binaries organized by <os>-<arch>/
go-cache/mod/ Go module cache (persisted across redeploys)
go-cache/build/ Go build cache (persisted across redeploys)
agent-source/ Sparse-checkout clone of the agent Go source

Note: Persistent disks disable Render's zero-downtime deploys. During a deploy, there will be brief downtime while the new container starts. This differs from Railway, which supports zero-downtime deploys with volumes.

Go cache persistence: The Go module and build caches (go-cache/) are stored on the persistent disk so that go mod download and compilation results survive container redeploys. Without this, every agent build would re-download all Go dependencies and recompile from scratch, adding 1-2 minutes per build.

Auto-Deploy

With GitHub integration, every push to your configured branch triggers a rebuild. The render.yaml uses buildFilter.paths to only rebuild a service when its relevant files change:

  • backend rebuilds on: src/**, package.json, Dockerfile, tsconfig.json
  • frontend rebuilds on: src/**, package.json, Dockerfile, nginx.conf, docker-entrypoint.sh

A commit touching only frontend/src/ will not trigger a backend redeploy.

Custom Domains

Three domains are needed for a production deployment: frontend, backend, and Clerk.

Step 1: Add Domains on Render

In each service's SettingsCustom Domains, add:

Service Custom Domain Example
Frontend <prefix>.yourdomain.com tpsgl.projectachilles.io
Backend <prefix>.agent.yourdomain.com tpsgl.agent.projectachilles.io

Step 2: DNS Records

Add CNAME records in your DNS provider:

Record Type Target
<prefix>.yourdomain.com CNAME <your-frontend>.onrender.com
<prefix>.agent.yourdomain.com CNAME <your-backend>.onrender.com
clerk.<prefix>.yourdomain.com CNAME frontend-api.clerk.services

The Clerk CNAME is configured in Clerk Dashboard → Domains when adding a production instance (see Clerk Setup below). Clerk provides the exact CNAME target.

Step 3: Update Environment Variables

After DNS propagates and Render provisions TLS certificates:

Variable Service New Value
CORS_ORIGIN Backend https://<prefix>.yourdomain.com
AGENT_SERVER_URL Backend https://<prefix>.agent.yourdomain.com
VITE_API_URL Frontend https://<prefix>.agent.yourdomain.com

Step 4: Update Clerk

In your Clerk application's Domains settings:

  • Add the frontend custom domain as an allowed origin
  • If using a production instance, the Clerk-hosted sign-in pages will use clerk.<prefix>.yourdomain.com automatically

Clerk Setup

Create a separate Clerk application for your Render deployment. Clerk development and production instances behave differently — production requires additional OAuth configuration.

Step 1: Create Application

  1. In dashboard.clerk.com, create a new application
  2. Enable desired social providers (GitHub, Google, etc.)
  3. Copy the publishable key (pk_test_...) and secret key (sk_test_...) to both services' environment variables

Step 2: Add Production Instance (for custom domains)

When you're ready to use custom domains instead of *.onrender.com:

  1. In Clerk Dashboard, go to ConfigureProduction
  2. Add your custom domain — Clerk will provide DNS records to add (a CNAME for clerk.<your-domain>)
  3. After DNS verification, Clerk generates production keys (pk_live_... / sk_live_...)
  4. Update CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY on both Render services with the production keys

Important: Development keys (pk_test_) and production keys (pk_live_) are not interchangeable. After switching to production, the development keys stop working for that instance.

Step 3: Configure OAuth Credentials (Production Only)

This step is critical. Clerk development instances use Clerk's shared OAuth credentials for social providers (GitHub, Google, etc.) — login works out of the box. Production instances require your own OAuth credentials. Without them, social login buttons will redirect to the provider with an empty client_id, resulting in a 404 error.

GitHub OAuth

  1. Go to github.com/settings/developersOAuth AppsNew OAuth App

  2. Configure:

    Field Value
    Application name ProjectAchilles - Render (or your preferred name)
    Homepage URL https://<your-frontend-domain>
    Authorization callback URL https://clerk.<your-domain>/v1/oauth_callback
  3. After creation, generate a Client Secret

  4. In Clerk Dashboard → ConfigureSSO ConnectionsGitHub:

    • Enter the Client ID and Client Secret
    • Save

Google OAuth

  1. Go to console.cloud.google.com/apis/credentials
  2. Create an OAuth 2.0 Client ID (Web application type)
  3. Add authorized redirect URI: https://clerk.<your-domain>/v1/oauth_callback
  4. In Clerk Dashboard → ConfigureSSO ConnectionsGoogle:
    • Enter the Client ID and Client Secret
    • Save

OAuth callback URL pattern: All social providers use https://clerk.<your-domain>/v1/oauth_callback as the redirect URI. Replace <your-domain> with your frontend's custom domain (e.g., clerk.tpsgl.projectachilles.io).

Agent Build from Source

The backend Docker image includes Go 1.24.3, so agent cross-compilation works on Render — same as Railway. Set AGENT_REPO_URL to your ProjectAchilles repo and the backend will git-clone the agent/ subdirectory at startup (sparse checkout), then use it for Go cross-compilation.

Variable Value Notes
AGENT_REPO_URL https://github.com/your-org/ProjectAchilles.git Required for agent builds
AGENT_REPO_BRANCH main Branch to clone from
GITHUB_TOKEN ghp_... Required if the repo is private (same token used for test library)

If AGENT_REPO_URL is not set, the backend disables the build feature and shows "Agent build from source is not available" in the UI. You can still upload pre-built binaries manually.

Cost Estimate

With Elastic Cloud handling analytics externally:

Service Est. Monthly Cost
Backend (Starter plan) ~$7
Frontend (Starter plan) ~$7
Persistent disk (1 GB) ~$0.25
Total ~$14

For comparison, Railway runs the same stack for ~$10-13/mo (usage-based pricing). Render's Starter plan is a flat rate. See Render pricing for current rates.

Helper Scripts

Generate all secrets at once:

./scripts/generate-secrets.sh --target render

Interactive setup wizard:

./scripts/setup.sh    # Select: PaaS → Render

Initialize Elasticsearch indices on Elastic Cloud:

./scripts/init-elasticsearch.sh --cloud-id "deploy:..." --api-key "..."

Troubleshooting

Frontend shows "502 Bad Gateway"

On Starter plan, this means nginx can't resolve the backend hostname (private network DNS is not available). Fix:

  • Set VITE_API_URL to the backend's public URL (e.g., https://achilles-backend.onrender.com) on the frontend service
  • Remove BACKEND_HOST and BACKEND_PORT if set — they require Standard plan or above

On Standard plan and above with nginx proxy mode:

  • Verify BACKEND_HOST is set to the backend's service name
  • Both services must be in the same Render region
  • The backend service must be running and healthy

Frontend shows CORS errors

The backend's CORS_ORIGIN doesn't match the frontend's origin. Verify:

  • CORS_ORIGIN is set to the frontend's full URL with scheme (e.g., https://achilles-frontend.onrender.com)
  • Do not use just the hostname — the https:// prefix is required

Encrypted settings lost after redeploy

ENCRYPTION_SECRET is not set. Without it, the backend derives a key from the container's hostname, which changes on every deploy. Set a stable ENCRYPTION_SECRET in the Render environment variables.

Agent enrollment commands show wrong URL

Set AGENT_SERVER_URL to the backend's public Render domain (with https://), e.g., https://achilles-backend.onrender.com.

Analytics shows "not configured"

The Elastic Cloud connection is stored in the encrypted analytics.json file. If ENCRYPTION_SECRET changed, the file becomes unreadable. Reconfigure via Analytics → Setup, or set ELASTICSEARCH_CLOUD_ID and ELASTICSEARCH_API_KEY as env vars (env vars take priority over the file).

GitHub/Google login returns 404 or fails

Symptom: Clicking "Sign in with GitHub" redirects to GitHub with client_id= (empty), and GitHub returns a 404 page. Google login may fail similarly.

Cause: You are using a Clerk production instance but haven't configured custom OAuth credentials. Production instances require your own GitHub/Google OAuth app credentials — unlike development instances, which use Clerk's shared credentials automatically.

Fix: Follow the "Configure OAuth Credentials" steps in the Clerk Setup section above. Every social provider enabled in Clerk needs its own Client ID and Client Secret when running in production mode.

Agent builds fail with "Agent source not found"

The AGENT_REPO_URL environment variable is not set, or the Git clone failed at startup. Check:

  • AGENT_REPO_URL is set to the full HTTPS URL of your ProjectAchilles repo
  • GITHUB_TOKEN is set if the repo is private (the token is injected into the clone URL)
  • The container logs for git clone errors (e.g., authentication failures, branch not found)

The agent source is cloned once at startup via sparse checkout (only the agent/ subdirectory). If the clone fails, the build feature is disabled and the Agent tab shows "Agent build from source is not available."

Build times are slow

Render caches Docker layers between builds. The first build takes 3-5 minutes; subsequent builds with only source code changes take ~1-2 minutes. If builds are consistently slow, check that buildFilter.paths is configured correctly so unrelated changes don't trigger rebuilds.

Go agent builds also benefit from persistent caches — the first agent build downloads all Go modules (~30s), but subsequent builds reuse the cached modules from the persistent disk.