Back to skills
extension
Category: Development & EngineeringNo API key required

local-frappe

Local Frappe development environment for testing APIs before production deployment. Use when developing or testing Python/API changes locally.

personAuthor: jakexiaohubgithub

/local-frappe - Local Frappe Development

CRITICAL: TEST LOCALLY FIRST - NO EXCEPTIONS

NEVER deploy Frappe/Python changes directly to production.

| Action | Time | When to Use | |--------|------|-------------| | Local test | Seconds | ALWAYS - every code change | | Production deploy | 15-20 min | ONLY after local testing passes |

The 15-20 minute production build/deploy cycle is UNACCEPTABLE for iterative development.

What This Command Does

Sets up and manages a local Frappe development environment for testing BEI custom APIs before deploying to production.

MANDATORY: Always test changes locally BEFORE pushing to production. The local environment matches production (Frappe v15, ERPNext v15, HRMS v15).

Quick Start

cd docker-dev
dev.bat start     # Windows (first run takes 10-15 minutes)
./dev.sh start    # Linux/Mac

Access

| URL | Purpose | |-----|---------| | http://localhost:8000 | Frappe web UI | | http://localhost:8000/api/method/frappe.ping | API test endpoint | | http://localhost:8000/api/method/hrms.api.hello.hello | BEI test API | | localhost:3307 | MariaDB (user: root, pass: admin) |

Login: Administrator / admin

Development Workflow

Standard Workflow (Edit → Sync → Test)

# 1. Edit your API file locally
#    Example: hrms/api/employee_clearance.py

# 2. Sync to container and clear cache
dev.bat sync

# 3. Test your API
curl http://localhost:8000/api/method/hrms.api.employee_clearance.get_status

# 4. When ready, commit and push to production
git add .
git commit -m "feat: Add employee clearance API"
git push origin production

Helper Commands

| Command | Description | |---------|-------------| | dev.bat start | Start all containers (first run: 10-15 min) | | dev.bat stop | Stop all containers | | dev.bat restart | Restart Frappe to pick up code changes | | dev.bat sync | Copy API files and clear cache (quick reload) | | dev.bat shell | Open bash shell in container | | dev.bat bench | Run bench command (e.g., dev.bat bench migrate) | | dev.bat logs | Follow Frappe logs | | dev.bat test | Test hello API endpoint | | dev.bat migrate | Run database migrations | | dev.bat status | Show container status | | dev.bat reset | Delete everything and start fresh (DESTRUCTIVE) |

File Structure

docker-dev/
├── docker-compose.yml    # Container orchestration
├── dev.bat               # Windows helper script
└── dev.sh                # Linux/Mac helper script

hrms/api/                 # BEI custom API files (synced to container)
├── __init__.py           # API exports (CRITICAL - update when adding new APIs)
├── hello.py              # Test endpoint
├── employee_clearance.py # Clearance APIs
├── enrichment.py         # Data enrichment APIs
├── google_chat.py        # Google Chat integration
├── google_drive.py       # Google Drive integration
├── google_login.py       # Google OAuth
├── oauth_tokens.py       # OAuth token management
├── onboarding.py         # Employee onboarding
└── roster.py             # Shift roster APIs

Adding a New API

1. Create your API file

# hrms/api/my_new_api.py
import frappe

@frappe.whitelist(allow_guest=True)
def hello():
    """Test endpoint."""
    return {"message": "Hello from my new API!"}

@frappe.whitelist()
def get_data():
    """Requires authentication."""
    return {"user": frappe.session.user}

2. Update init.py

# hrms/api/__init__.py
from hrms.api.my_new_api import (
    hello,
    get_data,
)

3. Sync and test

dev.bat sync
curl http://localhost:8000/api/method/hrms.api.my_new_api.hello

Container Configuration

| Component | Value | |-----------|-------| | Frappe Image | frappe/bench:v5.25.11 | | Bench Location | /workspace/frappe-bench | | API Mount | /bei-api (read-only from hrms/api/) | | Site Name | dev.localhost | | MariaDB Port | 3307 (external), 3306 (internal) | | Web Port | 8000 | | Socket.IO Port | 9000 |

Volumes

| Volume | Purpose | |--------|---------| | docker-dev_frappe-bench | Frappe bench directory (apps, sites, etc.) | | docker-dev_mariadb-data | Database data |

Note: Volumes persist data across container restarts. To completely reset, use docker compose down -v.

Troubleshooting

Container won't start

# Check logs
dev.bat logs

# Or directly
docker logs frappe-dev

API changes not visible

# Force sync and clear cache
dev.bat sync

# If still not working, restart Frappe
dev.bat restart

Database connection errors

# Check MariaDB is healthy
docker ps

# Should show mariadb-dev as (healthy)

Need fresh start

# WARNING: This deletes all data
dev.bat reset
# Or manually:
docker compose -f docker-dev/docker-compose.yml down -v
dev.bat start

First run taking too long

First run downloads and installs:

  • Frappe Framework (~3 min)
  • ERPNext (~5 min)
  • HRMS (~3 min)
  • Site creation (~2 min)

Total: 10-15 minutes. Subsequent starts are fast (~30 seconds).

Testing APIs

Without authentication

# APIs with @frappe.whitelist(allow_guest=True)
curl http://localhost:8000/api/method/hrms.api.hello.hello

With authentication

# Get session cookie first
curl -c cookies.txt -X POST http://localhost:8000/api/method/login \
  -d "usr=Administrator" -d "pwd=admin"

# Then use the cookie
curl -b cookies.txt http://localhost:8000/api/method/hrms.api.roster.get_roster

Using bench execute

# Run Python code directly
dev.bat bench execute hrms.api.hello.hello

# With arguments
dev.bat bench execute "hrms.api.employee_clearance.get_status" --args '["HR-EMP-0001"]'

When to Deploy to Production

After testing locally:

  1. Commit changes:

    git add hrms/api/
    git commit -m "feat: Add employee clearance API"
    
  2. Push to production branch:

    git push origin production
    
  3. Monitor GitHub Actions: https://github.com/Bebang-Enterprise-Inc/hrms/actions

  4. Verify production:

    curl https://hq.bebang.ph/api/method/hrms.api.hello.hello
    

See /deploy-frappe skill for full production deployment options.

DO NOT DO

  1. NEVER edit files directly in the container - Changes are lost on restart
  2. NEVER use docker commit - Corrupts the Python environment
  3. NEVER push untested code to production - Always test locally first
  4. NEVER skip updating __init__.py - API won't be whitelisted
  5. NEVER assume production deploy will pick up code changes - Use no_cache=true (see below)

CRITICAL: Local vs Production Asset Handling

Why Local Works But Production Breaks

Local development allows bench build because:

  • Single container environment
  • No persistent asset volumes (fresh start each time)
  • Development mode reloads assets on change

Production prohibits bench build because:

  • Multi-container architecture (frontend + backend)
  • Persistent volumes don't auto-update
  • Assets baked into Docker image at build time

What This Means For You

| Action | Local | Production | |--------|-------|------------| | bench build | ✅ OK | ❌ NEVER | | bench migrate | ✅ OK | ⚠️ Only via isolated container | | Edit Python files | ✅ Sync + restart | ❌ Rebuild image | | Edit CSS/JS | ✅ Sync + restart | ❌ Rebuild image |

The Production Rule

All code and asset changes MUST go through Docker image rebuild.

When you push to production:

  1. GitHub Actions rebuilds the entire Docker image
  2. New CSS/JS hashes are baked into image
  3. Assets volume should be deleted before deploy
  4. Fresh containers get consistent assets

Never "hot-fix" production by running bench commands.

See /deploy-frappe for the full CSS 404 root cause analysis and fix.

Testing Custom WWW Pages Locally

Creating Custom Pages (like login)

  1. Create your page file:

    hrms/www/bei-login.html  # The HTML template
    hrms/www/bei-login.py    # Optional Python context handler
    
  2. If redirecting from an existing route, add to hrms/hooks.py:

    website_redirects = [
        {"source": "/login", "target": "/bei-login", "redirect_http_status": 302},
    ]
    
  3. Sync to container:

    dev.bat sync
    
  4. Test locally:

    # Check redirect
    curl -sI http://localhost:8000/login | grep -i location
    
    # Check page renders
    curl -s http://localhost:8000/bei-login | grep -o "<title>.*</title>"
    

Important: Production Deployment Uses Docker Cache

After testing locally, you MUST use no_cache=true when deploying:

gh workflow run "Build and Deploy Frappe HRMS" --repo Bebang-Enterprise-Inc/hrms -f no_cache=true -f run_migrate=true

Why: Docker cache doesn't detect when git repo contents change. Without no_cache=true, the build serves cached layers from previous builds.

How to verify fresh build: Check GitHub Actions build time:

  • ~2 min = CACHED (old code)
  • ~5-10 min = FRESH (new code)

Running bei-tasks Frontend Locally (Added 2026-01-24)

For full-stack local development, run both Frappe AND bei-tasks locally:

1. Start Local Frappe

cd F:\Dropbox\Projects\BEI-ERP\docker-dev
powershell -Command "docker compose up -d"
# Wait for Frappe to start (~30 seconds if already initialized)

2. Generate API Keys (First Time Only)

# Login to get session cookie
curl -c cookies.txt -X POST "http://localhost:8000/api/method/login" \
  -d "usr=Administrator&pwd=admin"

# Generate API keys
curl -b cookies.txt -X POST \
  "http://localhost:8000/api/method/frappe.core.doctype.user.user.generate_keys?user=Administrator"
# Returns: {"message":{"api_key":"xxx","api_secret":"yyy"}}

3. Configure bei-tasks for Local Frappe

Create/update F:\Dropbox\Projects\bei-tasks\.env.development.local:

# Local Development Environment
NEXT_PUBLIC_FRAPPE_URL=http://localhost:8000

# Local Frappe API credentials (from step 2)
FRAPPE_API_KEY=your-api-key
FRAPPE_API_SECRET=your-api-secret

# App Configuration
NEXT_PUBLIC_APP_NAME=BEI Tasks (LOCAL)

4. Start Local bei-tasks

cd F:\Dropbox\Projects\bei-tasks
npm run dev
# Frontend runs at http://localhost:3000

5. Test the Connection

# Test Frappe API directly
curl -H "Authorization: token api-key:api-secret" \
  "http://localhost:8000/api/method/hrms.api.onboarding.get_session?token=test"

# Expected: {"message":{"success":false,"error":"Session not found",...}}

Local Development Stack

| Component | URL | Purpose | |-----------|-----|---------| | Frappe Backend | http://localhost:8000 | API + Admin UI | | bei-tasks Frontend | http://localhost:3000 | React/Next.js app | | MariaDB | localhost:3307 | Database | | Socket.IO | localhost:9000 | Realtime |

Syncing Code Changes

# After editing hrms/api/*.py files:
cd F:\Dropbox\Projects\BEI-ERP\docker-dev

# Sync files and clear cache
powershell -Command "docker exec frappe-dev bash -c 'cp -f /bei-api/*.py /workspace/frappe-bench/apps/hrms/hrms/api/ && cd /workspace/frappe-bench && bench --site dev.localhost clear-cache'"

# If whitelist decorators changed, restart Frappe
powershell -Command "docker restart frappe-dev"

Important Notes

  1. Authentication Required: Local APIs need authentication (no allow_guest=True)
  2. Session Cookie OR Token Auth: Use either method for local testing
  3. Different Site: Local uses dev.localhost, production uses hq.bebang.ph
  4. No Test Data: Local DB is empty - create test data as needed

Related Skills

  • /deploy-frappe - Production deployment (ONLY after local testing)
  • /frappe-sql-bulk - Bulk data operations
  • /frappe-doctype - DocType development

Production Testing Infrastructure (Added 2026-01-23)

CRITICAL: Database Architecture Discovery

The Frappe system at hq.bebang.ph uses a LOCAL Docker database via Docker Swarm (migrated 2026-01-29).

| Component | Service Name | Purpose | |-----------|--------------|---------| | Backend | frappe_backend | Frappe/ERPNext/HRMS (Gunicorn) | | Database | frappe_db | MariaDB (LOCAL) | | Frontend | frappe_frontend | Nginx proxy | | WebSocket | frappe_websocket | Socket.IO | | Queue | frappe_queue-short, frappe_queue-long | Background workers | | Scheduler | frappe_scheduler | Cron jobs |

Note: Production now uses Docker Swarm (9 services). Container names changed from frappe_docker-backend-1 to dynamic names under frappe_backend.1.xxxx.

Common Mistake: AWS SSM commands often connect to the RDS database (frappe-hrms-db.ctmwomgscn66.ap-southeast-1.rds.amazonaws.com), but the live Frappe instance uses the local Docker database. This causes "no employees found" issues when testing.

Production Test Accounts

| Employee ID | Email | Password | Role | |-------------|-------|----------|------| | TEST-STAFF-001 | test.staff@bebang.ph | BeiTest2026! | Store Staff | | TEST-SUPERVISOR-001 | test.supervisor@bebang.ph | BeiTest2026! | Store Supervisor | | TEST-AREA-001 | test.area@bebang.ph | BeiTest2026! | Area Supervisor | | TEST-HR-001 | test.hr@bebang.ph | BeiTest2026! | HR User |

Inserting Test Data into Production

# 1. Get AWS credentials
export AWS_ACCESS_KEY_ID=$(doppler secrets get AWS_ACCESS_KEY_ID --project bei-erp --config dev --plain)
export AWS_SECRET_ACCESS_KEY=$(doppler secrets get AWS_SECRET_ACCESS_KEY --project bei-erp --config dev --plain)
export AWS_DEFAULT_REGION=ap-southeast-1

# 2. Run SQL on the CORRECT database (via Swarm backend container)
aws ssm send-command \
  --instance-ids "i-026b7477d27bd46d6" \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=[
    "docker exec $(docker ps -qf name=frappe_backend) bench --site hq.bebang.ph mariadb --execute \"INSERT INTO tabEmployee (name, employee_name, first_name, status, gender, date_of_birth, date_of_joining, company, user_id, creation, modified, owner, modified_by) VALUES ('TEST-STAFF-001', 'Test Staff', 'Test', 'Active', 'Male', '1990-01-01', '2024-01-01', 'Bebang Enterprise Inc.', 'test.staff@bebang.ph', NOW(), NOW(), 'Administrator', 'Administrator');\""
  ]'

# 3. Verify
aws ssm send-command \
  --instance-ids "i-026b7477d27bd46d6" \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=[
    "docker exec $(docker ps -qf name=frappe_backend) bench --site hq.bebang.ph mariadb --execute \"SELECT name, employee_name, user_id FROM tabEmployee WHERE name LIKE 'TEST-%';\""
  ]'

API Credentials for bei-tasks

| Environment | Key | Secret | Source | |-------------|-----|--------|--------| | Production (Vercel) | 4a17c23aca83560 | 38ecc0e1054b1d2 | Doppler bei-erp | | Local (.env.local) | Same | Same | Doppler bei-erp |

IMPORTANT: HR API routes use token auth (not session cookies) for Employee lookups because regular Frappe users don't have Employee read permission.

Verifying API Connection

# Test from production
curl -X POST https://hq.bebang.ph/api/resource/Employee \
  -H "Authorization: token 4a17c23aca83560:38ecc0e1054b1d2" \
  -H "Content-Type: application/json" \
  -d '{"filters": [["user_id", "=", "test.staff@bebang.ph"]]}'

# Expected: Returns employee record

Troubleshooting Production Tests

  1. "No employee found" but employee exists:

    • Check you're querying the Docker database, not RDS
    • Use bench --site hq.bebang.ph mariadb not direct RDS connection
  2. API returns 401:

    • Verify Vercel env vars match Doppler
    • Redeploy after env var changes
  3. Container not found:

    • Production now uses Docker Swarm (migrated 2026-01-29)
    • Service names: frappe_backend, frappe_frontend, etc.
    • Container names are dynamic: frappe_backend.1.xxxxx
    • Use docker service ls to verify services
    • Use docker ps -qf name=frappe_backend to get container ID
  4. HR pages show "Failed to get employee data":

    • HR APIs need token auth, not session auth
    • Check app/api/hr/*/route.ts uses Authorization: token header
  5. Code deployed but changes not visible:

    • Docker build caching issue - use no_cache=true when deploying
    • See /deploy-frappe for details on when to use no_cache
    • Verify with: curl https://hq.bebang.ph/api/method/hrms.api.hello.hello
    • Response should include build_version field showing deployment timestamp

Deployment Verification Endpoint

The hello API includes deployment info for verification:

curl -s https://hq.bebang.ph/api/method/hrms.api.hello.hello | python -m json.tool

Expected response:

{
    "message": {
        "message": "Hello from Frappe HRMS!",
        "timestamp": "2026-01-29 10:04:11.569399",
        "build_version": "2026-01-29T12:16:00+08:00",
        "deployment": "docker-swarm"
    }
}

If build_version doesn't update after deployment, rebuild with no_cache=true.