---
title: "Deploy Frappe HR | (Just Updated) Open-Source HRMS, Payroll & Leave, API Key Preset"
description: "Frappe HR open-source HRMS: payroll, leave, attendance on ERPNext + API"
category: "Other"
url: https://railway.com/deploy/frappe-hr-or-just-updated-open-source-hr
---

# 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 on Railway](https://railway.com/template/frappe-hr-or-just-updated-open-source-hr)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/frappe-hr-or-just-updated-open-source-hr/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** Other

## Template content

### frappe-hr

- **Image:** ghcr.io/bon5co/frappe-hr-railway:latest
- **Public domain:** Yes

### mariadb

- **Image:** ghcr.io/bon5co/erpnext-railway-mariadb:latest

## Documentation

# 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

```bash
# 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](https://github.com/frappe/hrms) - the upstream HR and payroll app (GPL-3.0)
- [ERPNext](https://github.com/frappe/erpnext) - the ERP the HR app builds on
- [Frappe Framework](https://github.com/frappe/frappe) - the application framework underneath both
- [frappe/hrms Docker image](https://github.com/frappe/frappe_docker/pkgs/container/hrms) - the upstream image this template pins by digest (frappe 15.126.0, erpnext 15.124.5, hrms 15.64.8)
- [bon5co/frappe-hr-railway](https://github.com/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:

| Variable | Default | What it does |
|---|---|---|
| `SITE_NAME` | `hr` | site directory name; changing it after first boot creates a second, empty site |
| `GUNICORN_WORKERS` | `2` | web concurrency |
| `GUNICORN_THREADS` | `4` | threads per web worker |
| `FRAPPE_WORKER_QUEUES` | `short,default,long` | queues the background worker consumes |
| `CLIENT_MAX_BODY_SIZE` | `50m` | upload 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:

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


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/frappe-hr-or-just-updated-open-source-hr
