Frappe Devstack

Batteries-included Docker Compose framework for Frappe v16: one CLI from zero to a running bench, plus a hardened production stack (Traefik + Let's Encrypt TLS, multi-tenancy, backups) and local staging.

Install Frappe Devstack

bench get-app https://github.com/Venkateshvenki404224/frappe-devstack

Tags

  • devops
  • docker
  • docker-compose
  • erpnext
  • frappe
  • frappe-framework
  • self-hosted
  • traefik

Add the Frappe Gems badge to your README

Maintain Frappe Devstack? Paste this into your README:

[![Listed on Frappe Gems](https://frappegems.com/api/method/frappe_gems.seo.badge?app=Venkateshvenki404224%2Ffrappe-devstack)](https://frappegems.com/gems/apps/Venkateshvenki404224/frappe-devstack)

About Frappe Devstack

frappe-devstack

A batteries-included Docker Compose framework for Frappe v16 — one CLI takes you from zero to a running bench locally, and the same repo ships a hardened production stack (Traefik + automatic TLS, multi-tenancy, backups) you can deploy to a real host.

You declare the apps you want in a small JSON preset; frappe-devstack builds the image, generates config, brings up the full runtime (backend, frontend, websocket, scheduler, queue workers, MariaDB, Redis), and creates a site with those apps installed.

CI  License: MIT


Why

frappe_docker is powerful but low-level. frappe-devstack wraps the same building blocks in an opinionated CLI so the common path is one command:

  • One preset → one command. List apps + branches in presets/.json; python entry.py setup does the rest.
  • Dev that's actually editable. App sources are cloned to ./apps/ and bind-mounted, so editing Python is live (docker compose restart backend) — no rebuild.
  • Private apps, safely. A ${GITHUB_PAT} placeholder in a preset URL is expanded from .env.secrets, stripped from the image build-arg, and supplied to BuildKit as a --mount=type=secret — so the token never lands in image layers or docker history.
  • Dev → staging → prod, same artifact. The production stack runs the immutable baked image; staging runs that exact image locally so you rehearse before the live host.

Requirements

  • Docker Engine 23+ with the Compose v2 plugin (BuildKit is on by default).
  • Python 3.8+ (standard library only — no pip install needed).
  • git.

Quick start (local dev)

git clone https://github.com/Venkateshvenki404224/frappe-devstack.git
cd frappe-devstack

# First run: build image, generate .env, start the stack, create a site.
python entry.py setup --preset example

# Day-to-day
python entry.py restart        # restart services
python entry.py stop           # stop services
python entry.py destroy        # tear down containers + volumes (prompts 'yes')

# Rebuild the image after editing a preset
python entry.py build --preset example

When setup finishes it prints the URL (default http://localhost:8080, auto-bumped if the port is taken), the Administrator login, and the generated password. Admin and DB passwords are generated on first run and written to .envnever commit .env.

> python entry.py with no arguments is the same as setup (the first-run wizard).


Presets

A preset is a JSON array of { "url", "branch" } objects — the apps baked on top of Frappe. The bundled presets/example.json:

App Branch
frappe version-16 (set by FRAPPE_VERSION, cloned automatically)
erpnext version-16
hrms version-16

Add more by editing the file (apps track their own branch lines — e.g. wiki uses version-3):

[
  { "url": "https://github.com/frappe/erpnext", "branch": "version-16" },
  { "url": "https://github.com/frappe/hrms",    "branch": "version-16" },
  { "url": "https://github.com/frappe/wiki",    "branch": "version-3"  }
]

After changing a preset, run python entry.py build --preset. The existing site won't auto-install newly added apps — install them explicitly:

docker compose exec backend bench --site frontend install-app 
docker compose exec backend bench --site frontend migrate

Create as many presets as you like (presets/.json) and select one with --preset.

Adding a private app (GitHub PAT)

Reference a private repo with a ${GITHUB_PAT} placeholder — see presets/private-app.example.json:

{ "url": "https://${GITHUB_PAT}@github.com/your-org/your-private-app.git", "branch": "main" }

Provide the token (a fine-grained, read-only token scoped to just that repo, Contents: read):

cp .env.secrets.example .env.secrets
# edit .env.secrets:  GITHUB_PAT=github_pat_xxx

entry.py reads .env.secrets at runtime (never writes to it; it's gitignored), expands the placeholder for the host clone into ./apps/, and for the image build strips the token from apps.json and passes it to BuildKit as a secret. If GITHUB_PAT is empty, public clones still work and a clear warning is printed for private ones.


How it works

Build-time vs. run-time apps

Two app installations stay in sync:

  1. Baked into the image — the multi-stage Dockerfile runs bench init against an apps.json passed as the credential-free APPS_JSON_BASE64 build-arg; the PAT (if any) arrives as a BuildKit secret. .git dirs are stripped from the result.
  2. Bind-mounted at runtimedocker-compose.yml mounts ./apps over /home/frappe/frappe-bench/apps, and the CLI clones the same apps there so containers see editable source.

Editing ./apps// is live for Python (docker compose restart backend). But bench init-time wiring and built assets come from the baked copy — so adding or removing an app requires a rebuild (python entry.py build).

Runtime topology

Boot order is gated by depends_on + healthchecks:

  • db (MariaDB 10.6, healthchecked)
  • configurator (one-shot) — writes common_site_config.json (db/redis endpoints) and apps.txt.
  • create-site (one-shot) — waits for db + redis, then bench new-site frontend --install-app.
  • backend (gunicorn), frontend (nginx → host PORT), websocket, scheduler, queue-long, queue-short.
  • redis-cache, redis-queue.

State lives in named volumes (db-data, redis-*-data, sites, assets, logs) that survive stop/restart and are wiped by destroy. ./apps/ is a host bind mount (gitignored) and persists across destroys — delete it manually for fresh clones.


Production & staging

This repo is not just a dev toy — it ships a separate, hardened production stack and a local staging rehearsal that runs the identical image.

# Production (Traefik + Let's Encrypt TLS, multi-tenancy, tuned MariaDB, backups)
cp .env.prod.example .env.prod        # set domains, email, passwords, registry
python entry.py prod build --preset example
python entry.py prod push             # → your container registry
python entry.py prod up
python entry.py prod new-site erp.example.com

# Staging — the SAME image locally, Traefik off, on http://localhost:8080
python entry.py prod build --preset example
python entry.py staging up
python entry.py staging new-site erp.localhost

Full guides: PRODUCTION.md and STAGING.md.


Repository layout

frappe-devstack/
├── entry.py                    # CLI launcher (thin)
├── frappe_devstack/            # CLI package — all logic lives here
│   ├── cli.py                  #   argparse command tree + dispatch
│   ├── wizard.py               #   first-run dev setup
│   ├── compose.py              #   dev lifecycle (build/stop/restart/destroy)
│   ├── config.py               #   env files, presets, ports, passwords
│   ├── images.py               #   image build (BuildKit secret, host clone)
│   ├── prod.py                 #   production build/push/deploy/new-site
│   ├── staging.py              #   local staging
│   └── ui.py                   #   terminal output helpers
├── Dockerfile                  # multi-stage frappe/build → frappe/base
├── docker-compose.yml          # dev stack
├── docker-compose.prod.yml     # standalone production stack
├── docker-compose.staging.yml  # staging overlay on the prod stack
├── presets/                    # app sets (example.json, private-app.example.json)
├── .env.example                # dev env keys (.env is generated)
├── .env.secrets.example        # GITHUB_PAT template
├── .env.prod.example           # production env template
├── .gitlab-ci.yml              # GitLab pipeline (build + push image)
└── .github/workflows/ci.yml    # GitHub Actions (lint + validate config)

CLI reference

```bash python entry.py [setup] # first-time dev bring-up (default) python entry.py setup --preset [--frappe-version version-16] [--port N] [--no-browser] python entry.py build --preset # rebuild dev image after editing a preset python entry.py stop | restart | destroy

python entry.py prod build --preset [--frappe-version version-16] python entry.py prod push python entry.py prod up | down python entry.py prod new-site

Related Developer Tools apps for Frappe & ERPNext

  • Frappe — Low code web framework for real world applications, in Python and Javascript
  • Frappe Docker — Docker environment for developing, deploying, and running Frappe applications (ERPNext and custom apps) in production and development
  • Builder — Craft beautiful websites effortlessly with an intuitive visual builder and publish them instantly
  • Bench — CLI to manage Multi-tenant deployments for Frappe apps
  • Frappe Ui — A set of components and utilities for rapid UI development
  • Press — Full service cloud hosting for the Frappe stack - powers Frappe Cloud
  • Gameplan — Open Source Discussions Platform for Remote Teams
  • Doppio — A Frappe app (CLI) to magically setup single page applications and Vue/React powered desk pages on your custom Frappe apps.