Deploy Frappe HR | (Just Updated) Open-Source HRMS, Payroll & Leave, API Key Preset

Frappe HR open-source HRMS: payroll, leave, attendance on ERPNext + API

Deploy Frappe HR | (Just Updated) Open-Source HRMS, Payroll & Leave, API Key Preset

Just deployed

/home/frappe/frappe-bench/sites

/var/lib/mysql

Deploy and Host Frappe HR on Railway

Frappe HR (the HRMS app, built on ERPNext and the Frappe framework) is a complete open-source HR and payroll system: employee records, leave and holiday management, attendance and shift scheduling, expense claims, payroll with salary structures and slips, recruitment, appraisals and onboarding. Every document type is exposed over a REST API. It is the open-source alternative to BambooHR, Gusto-style payroll tools and per-seat HRIS subscriptions.

This template deploys Frappe HR (frappe 15, erpnext 15, hrms 15) as two services, the application and MariaDB, off images pinned to an exact version. There is no build step: both images are prebuilt, so the deploy is a pull. The deploy form has nothing you have to fill in, and the Administrator account already has an API key and secret.

About Hosting Frappe HR

Frappe HR is not one process. A working install needs the web server, a background worker for every queued job (emails, payroll runs, scheduled reports), a scheduler for cron-driven documents, a socket.io service for realtime updates, redis for the cache and the job queues, and nginx in front to serve the built assets and route the websocket. frappe_docker splits those across six containers around a shared sites/ volume.

A Railway volume attaches to exactly one service, and frappe's web, worker and scheduler all read and write the same sites/ directory. So on Railway, Frappe HR is a single container by necessity: this template runs the full process set under honcho, exactly as bench start does, and if any one process exits the container is restarted rather than left half-running. Redis runs inside that container, which is why you pay for two services and not four.

What this template does differently from a stock ERPNext deploy with the HR app added by hand:

  • HRMS is installed at first boot. The site is created with erpnext and hrms together, so the Leave Type, Salary Structure, Payroll Entry and Attendance doctypes exist from the first login. Nothing to install from the app store.
  • The API key pair is preset. Administrator gets FRAPPE_API_KEY and FRAPPE_API_SECRET from the service variables on every boot, so a script or an AI agent can call the REST API with a token header and never opens the login page.
  • Custom domains work without re-creating the site. The site directory is named hr and nginx passes that fixed name as X-Frappe-Site-Name, so frappe resolves the same site whatever Host it arrives with.

Sizing: expect roughly 500-600 MB for the application container at idle and 250-350 MB for MariaDB, so the Hobby plan is enough to start. GUNICORN_WORKERS and FRAPPE_WORKER_QUEUES are ordinary Railway variables.

First boot takes about 5 minutes. The entrypoint runs bench new-site --install-app erpnext --install-app hrms, which creates the database and builds the doctypes of three apps before anything listens on a port. The template sets no healthcheck, because Railway's 300 second default would race it. Every later boot finds the existing site and starts in seconds.

Agent contract

  • Public URL: https://$RAILWAY_PUBLIC_DOMAIN (the frappe-hr service's generated domain)
  • Private URL: http://frappe-hr.railway.internal:8080
  • Credentials: API key in the variable FRAPPE_API_KEY, API secret in FRAPPE_API_SECRET; browser login is Administrator with the password in ADMIN_PASSWORD
  • Auth: send the header Authorization: token FRAPPE_API_KEY:FRAPPE_API_SECRET on every API call; a request without it answers 403, a wrong key answers 401
  • Health: GET /api/method/ping returns 200 {"message":"pong"} without authentication
# needs: RAILWAY_PUBLIC_DOMAIN of the frappe-hr service, plus FRAPPE_API_KEY and FRAPPE_API_SECRET from its variables
BASE="https://$RAILWAY_PUBLIC_DOMAIN"
AUTH="Authorization: token $FRAPPE_API_KEY:$FRAPPE_API_SECRET"
curl -s -w "\nhealth: %{http_code}\n" "$BASE/api/method/ping"
curl -s -H "$AUTH" "$BASE/api/method/frappe.auth.get_logged_user"; echo
# HRMS is installed: its Leave Type doctype ships with default leave types
curl -s -H "$AUTH" "$BASE/api/resource/Leave%20Type?limit_page_length=3"; echo
NAME="Agent Leave $(date +%s)"
curl -s -o /dev/null -w "write without key: %{http_code}\n" -X POST "$BASE/api/resource/Leave%20Type" -H 'Content-Type: application/json' -d "{\"leave_type_name\":\"$NAME\"}"
curl -s -o /dev/null -w "write with wrong key: %{http_code}\n" -X POST "$BASE/api/resource/Leave%20Type" -H "Authorization: token bad:bad" -H 'Content-Type: application/json' -d "{\"leave_type_name\":\"$NAME\"}"
curl -s -o /dev/null -w "write with key: %{http_code}\n" -X POST "$BASE/api/resource/Leave%20Type" -H "$AUTH" -H 'Content-Type: application/json' -d "{\"leave_type_name\":\"$NAME\",\"max_leaves_allowed\":5}"
curl -s -H "$AUTH" "$BASE/api/resource/Leave%20Type/$(printf %s "$NAME" | sed 's/ /%20/g')" | jq -r .data.leave_type_name

A fresh deploy prints {"message":"pong"} and health: 200, then {"message":"Administrator"}, three default leave types (Casual Leave, Compensatory Off, Sick Leave), write without key: 403, write with wrong key: 401, write with key: 200, and the new leave type's name read back.

Why Deploy Frappe HR on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Frappe HR on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

  • No build minutes and no drift - prebuilt images mean the deploy is a pull, and the version you deployed is the version that redeploys later.
  • Nothing to configure - the database host, the root password, the Administrator password and the API key pair are all generated and wired at deploy time. The form is empty.
  • A cheap HR system to leave running - two always-on containers instead of four, on a platform that bills what you use rather than a per-employee price.
  • Your data, your domain - attach a custom domain in the Railway UI and it works; employee records and uploads live on your volume.

Common Use Cases

  • Replacing a per-employee HRIS subscription - employee records, leave balances and approvals, attendance, and payroll in one self-hosted system.
  • Payroll for a small company - salary structures, payroll entries and salary slips generated from attendance and leave, with the accounting side in the same ERPNext site.
  • An HR back office your own software drives - the REST API covers every doctype, so an onboarding flow or an internal bot can create employees, leave applications and expense claims with a token header.

Dependencies for Frappe HR Hosting

  • MariaDB 11.8 (included in this template) on a volume at /var/lib/mysql, configured for utf8mb4.
  • Redis (included, inside the application container) for the cache, the job queues and realtime.
  • A volume for sites/ (included) at /home/frappe/frappe-bench/sites - the site database config, every uploaded file and every generated print format live there.
  • An SMTP provider - optional and not part of the template. Frappe HR sends leave notifications and salary slips by email; add an Email Account under Settings after deploying.

Deployment Dependencies

  • Frappe HR - the upstream HR and payroll app (GPL-3.0)
  • ERPNext - the ERP the HR app builds on
  • Frappe Framework - the application framework underneath both
  • frappe/hrms Docker image - the upstream image this template pins by digest (frappe 15.126.0, erpnext 15.124.5, hrms 15.64.8)
  • bon5co/frappe-hr-railway - the open-source Railway wrapper image (Dockerfile, entrypoint, Procfile) this template deploys

Implementation Details

Wait for the frappe-hr service to go green. The first deploy is creating the site, and the log line railway-entrypoint: site created marks the end of it. Then open your Railway domain and log in:

  • Username: Administrator
  • Password: the value of ADMIN_PASSWORD on the frappe-hr service (Variables tab)

The setup wizard runs on first login and asks for your company name, currency and chart of accounts. Change the Administrator password from Settings once you are in. The API key pair stays in force until you change FRAPPE_API_KEY or FRAPPE_API_SECRET; redeploying applies the new values.

Useful variables on the frappe-hr service, all with working defaults:

VariableDefaultWhat it does
SITE_NAMEhrsite directory name; changing it after first boot creates a second, empty site
GUNICORN_WORKERS2web concurrency
GUNICORN_THREADS4threads per web worker
FRAPPE_WORKER_QUEUESshort,default,longqueues the background worker consumes
CLIENT_MAX_BODY_SIZE50mupload size limit

Do not set a start command on either service. Both images do their Railway-specific work in their ENTRYPOINT (the volume chown, the site creation, the MariaDB configuration), and a Railway start command replaces ENTRYPOINT rather than adding to it.

Check the stack is healthy at any time:

curl https://your-app.up.railway.app/api/method/ping
# {"message":"pong"}

Template Content

More templates in this category

View Template
Rocky Linux
Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀

codestorm
51
View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

Lucas
71
View Template
Letta Code Remote
Run a Letta Code agent 24/7. No inbound ports, just deploy.

Letta
52