Home

Blog

Home

Blog

Install ERPNext 16 on Windows (WSL): A Complete Development Guide in 2026

15 min read
By David Muraya • February 15, 2026
Illustration of VS Code connected to Ubuntu WSL for ERPNext Development

Developing for Frappe and ERPNext requires a Linux environment due to its heavy reliance on Redis, MariaDB, and Unix-specific Python libraries. Historically, this forced Windows developers to use slow Virtual Machines or dual-boot their systems.

With WSL 2 (Windows Subsystem for Linux), you can now run a full Ubuntu environment directly on Windows with near-native performance. This allows you to write code in VS Code on Windows while your app runs in a real Linux environment just like your production server.

This guide covers setting up a high-performance local development environment for ERPNext Version 16 on Windows 10/11 using Ubuntu 24.04 LTS. If you are looking to deploy to production, check out our guide on Installing ERPNext on Ubuntu 24 or Deploying ERPNext with Docker.

Phase 0: The Development Stack

We are building a "Hybrid" workflow:

  1. Windows: Hosts your browser, terminal (via Windows Terminal), and code editor.
  2. WSL (Ubuntu): Hosts the database, Python runtime, Redis, and the Bench CLI.
  3. VS Code Remote: Bridges the two, allowing you to edit files inside Linux as if they were local.

1. Enable WSL & Install Ubuntu 24.04

Open PowerShell as Administrator and run:

wsl --install

If you already have WSL but need to upgrade to version 2 (mandatory for performance), refer to the official Microsoft WSL documentation:

wsl --set-default-version 2

Restart your computer if prompted.

Launch "Ubuntu" from your Start menu and create your UNIX root username and password when prompted.

Tip: When creating your username, avoid using root or admin. A standard name like david or developer is perfect.

2. Install VS Code & Extensions

To make this workflow seamless, you need:

  1. Visual Studio Code installed on Windows.
  2. The WSL Extension (by Microsoft) installed in VS Code.

This allows you to open any folder inside Ubuntu by typing code . in your terminal.

Phase 1: System Preparation (Inside WSL)

From this point on, all commands are run inside your Ubuntu terminal.

1. Update & Install Core Dependencies

We need the basic build tools, Git, and MariaDB database services.

sudo apt update && sudo apt upgrade -y

sudo apt install -y git redis-server mariadb-server mariadb-client \
pkg-config libmariadb-dev gcc build-essential libssl-dev \
python3-dev python3-setuptools python3-pip xvfb libfontconfig nano curl

2. Verify Systemd (Critical)

WSL 2 now supports systemd, which is required for MariaDB and Redis to run automatically. Check if it's active:

systemctl list-unit-files --type=service

If you get an error, edit /etc/wsl.conf and add:

[boot]
systemd=true

Then restart WSL (wsl --shutdown in PowerShell).

Phase 2: PDF Generation Support

ERPNext uses wkhtmltopdf (with patched Qt) to generate PDF reports. Since this package was dropped from Ubuntu 24 repositories, we install it manually. This ensures you can generate invoices and reports locally, a common requirement for ERP development.

# 1. Install libssl dependency (Required for legacy wkhtmltopdf)
wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb && \
sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb && \

# 2. Install wkhtmltopdf
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_amd64.deb && \
sudo dpkg -i wkhtmltox_0.12.6.1-3.jammy_amd64.deb && \

# 3. Fix missing dependencies
sudo apt --fix-broken install -y

Phase 3: Database Configuration

Developers often encounter issues with emoji support or strict mode in MariaDB. Let's configure it correctly from the start.

1. Apply Config

sudo tee /etc/mysql/mariadb.conf.d/99-frappe.cnf > /dev/null <<'EOF'
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

[mysql]
default-character-set = utf8mb4
EOF

Restart the service:

sudo systemctl restart mariadb

2. Secure & Set Root Password

Running mysql_secure_installation is recommended even for local dev to avoid permission issues later.

sudo mariadb-secure-installation

Follow the prompts (Set root password, remove anonymous users, remove test db).

Phase 4: Modern Python & Node Setup

For a stable development environment, we use nvm (Node Version Manager) and uv (Python manager). This allows you to switch versions easily if you work on multiple projects.

1. Install Node.js (via NVM)

We recommend installing Node using nvm (Node Version Manager) to manage versions easily. While modern Frappe setups (Version 16) perform best with Node 24, managing multiple versions is often necessary.

# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# Close and reopen terminal, or source the config
source ~/.bashrc

# Install Node 24
nvm install 24
nvm use 24
nvm alias default 24

# Verify version (Should be v24.x.x)
node -v

# Install Yarn
npm install -g yarn

2. Install Python 3.14 & Bench (via UV)

We use uv to manage Python versions without messing with the system Python. uv is significantly faster than pip and handles virtual environments automatically.

# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc

# Install Python 3.14
uv python install 3.14

# Install Bench CLI
# Bench is the CLI tool for Frappe Framework: https://frappeframework.com/docs/user/en/bench
uv tool install frappe-bench --python python3.14

# Confirm installation
bench --version

Phase 5: Initialize Your Bench

Now we create the folder that will hold all your apps and sites.

cd ~
bench init frappe-bench --frappe-branch version-16 --python python3.14
cd frappe-bench

Open in VS Code

Now is the perfect time to open your project in VS Code on Windows.

code .

You will see VS Code open with a connection to WSL: Ubuntu. You can now edit files, use the integrated terminal, and use Git GUI tools directly.

Phase 6: Create a Local Site

In development, we map a site to localhost.

1. Get ERPNext

bench get-app --branch version-16 erpnext

2. Create Site

bench new-site dev.localhost --admin-password 'admin' --mariadb-root-password 'YourRootPass'

3. Install App

bench --site dev.localhost install-app erpnext

4. Enable Developer Mode

This ensures any changes you make to DocTypes are saved to the file system (JSON files) so Git can track them.

bench --site dev.localhost set-config developer_mode 1

Phase 7: Running the Server

To start your development server, run:

bench start

Open your browser in Windows and go to: http://localhost:8000.


Phase 8: Creating Custom Apps & Version Control

Now that your development environment is set up, let's create a custom app for your ERPNext modifications. The best practice is to treat each custom app as its own Git repository for easy versioning, collaboration, and deployment.

1. Create a New Custom App

From your bench directory:

cd ~/frappe-bench

# Create a new app scaffold (replace 'my_custom_app' with your app name; use lowercase + underscore)
bench new-app my_custom_app

Follow the prompts:

  • App Title: A human-readable name (e.g., "My Custom App")
  • Description: Brief description of your app
  • Publisher: Your name or organization
  • Email: Your email address (e.g., you@example.com)
  • License: Choose an appropriate license (e.g., MIT)

This creates apps/my_custom_app/ with the Frappe app scaffold, including basic structure for doctypes, modules, and hooks.

2. Set Up Git Repository

Turn your app into a Git repo and push to GitHub:

cd apps/my_custom_app

# Initialize Git
git init
git add .
git commit -m "Initial app scaffold"

# Create a new repository on GitHub (e.g., github.com/yourusername/my_custom_app)
# Then add remote and push
git remote add origin git@github.com:yourusername/my_custom_app.git
git branch -M main
git push -u origin main

Tip: Use SSH keys for GitHub authentication to avoid password prompts. Generate them with ssh-keygen and add the public key to your GitHub account.

3. Install the App on Your Site

# From bench root
cd ~/frappe-bench

# Install on your site
bench --site dev.localhost install-app my_custom_app

# Build frontend assets
bench build

# Apply database migrations
bench --site dev.localhost migrate

Your custom app is now active on the site.

4. Enable Developer Mode

For UI-based development, enable developer mode:

# Edit site config
nano sites/dev.localhost/site_config.json

Add this line (if not already present):

{
  "developer_mode": 1
}

Restart your bench:

bench restart

5. Create DocTypes (UI-First Approach)

In your browser at http://localhost:8000:

  1. Go to Developer > DocType > New
  2. Set Module to a module in your custom app (create one via Developer > Module Def if needed)
  3. Design your fields, permissions, and other settings
  4. Save the DocType

6. Export DocType to Code

After creating in the UI, export to your app's code:

# From bench root
bench --site dev.localhost export-json "Your DocType Name" \
  apps/my_custom_app/my_custom_app/doctype/your_doctype/your_doctype.json

Commit the changes:

cd apps/my_custom_app
git add .
git commit -m "Add Your DocType"
git push

7. .gitignore for Security

Create .gitignore in your app repo:

__pycache__/
*.pyc
*.pyo
*.pyd
.DS_Store
.idea/
.vscode/
node_modules/
dist/
*.log
env/
.env
*.sqlite3
*.db

Critical: Never commit sites/*/site_config.json or sites/*/private/ as they contain database passwords and secrets. These files are outside your app repository (in the bench root), so they won't be included unless you mistakenly add them. If you version your entire bench directory, add sites/*/ to your bench-level .gitignore.

Phase 9: Development Workflow

Local Development

  1. Create a feature branch:

    git checkout -b feature/add-new-doctype
    
  2. Make changes in apps/my_custom_app/ (add doctypes, Python code, JavaScript, etc.)

  3. Test locally:

    # Build assets if you changed frontend code
    bench build
    
    # Apply migrations
    bench --site dev.localhost migrate
    
    # Restart server
    bench restart
    
  4. Commit and push:

    git add .
    git commit -m "Implement new feature"
    git push origin feature/add-new-doctype
    
  5. Create a Pull Request on GitHub for review.

Continuous Integration Pipeline

For a real-world CI/CD setup, use GitHub Actions to automate testing and deployment. Create .github/workflows/ci.yml in your app repository.

If you are interested in automating Python scripts, check out our guide on Running Python Scripts with GitHub Actions.

name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      mariadb:
        image: mariadb:11.8
        env:
          MYSQL_ROOT_PASSWORD: root
        ports:
          - 3306:3306
        options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3
      redis:
        image: redis:alpine
        ports:
          - 6379:6379

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.14'

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '24'

      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

      - name: Install Bench
        run: |
          uv tool install frappe-bench --python python3.14
          # Add uv tools to path for subsequent steps
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Initialize Bench
        run: |
          bench init --skip-redis-config-generation --skip-assets --python python3.14 frappe-bench

      - name: Install ERPNext
        working-directory: frappe-bench
        run: |
          bench get-app --branch version-16 erpnext

      - name: Install Custom App
        working-directory: frappe-bench
        run: |
          # We symlink the checked-out code into the apps directory
          # This allows us to test the current commit without re-cloning
          mkdir -p apps
          ln -s $GITHUB_WORKSPACE apps/my_custom_app

          # Install the app dependency
          uv pip install --python env/bin/python -e apps/my_custom_app

      - name: Create Site
        working-directory: frappe-bench
        run: |
          bench new-site test_site \
            --mariadb-root-password root \
            --admin-password admin \
            --no-mariadb-socket \
            --db-host 127.0.0.1 \
            --install-app erpnext

      - name: Install Custom App on Site
        working-directory: frappe-bench
        run: |
          bench --site test_site install-app my_custom_app

      - name: Run Tests
        working-directory: frappe-bench
        run: |
          bench --site test_site run-tests --app my_custom_app

This pipeline:

  • Sets up a clean environment on each push/PR
  • Initializes a test bench with your app
  • Runs your app's tests
  • Builds frontend assets
  • Can be extended for deployment to staging/production servers

Deployment to Production (Optional)

To deploy your app to a production server, add a deployment job that uses SSH. First, set up these additional secrets:

  • SSH_HOST: Your production server IP/hostname
  • SSH_USER: SSH username
  • SSH_KEY: Private SSH key (add as a secret, not a variable)
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'  # Only deploy on main branch
    steps:
    - name: Deploy to Production
      uses: appleboy/ssh-action@v1.0.3
      with:
        host: ${{ secrets.SSH_HOST }}
        username: ${{ secrets.SSH_USER }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          cd /path/to/your/bench
          bench get-app my_custom_app https://github.com/yourusername/my_custom_app.git
          bench --site yoursite.com install-app my_custom_app
          bench --site yoursite.com migrate
          bench build
          bench restart

Developer FAQ

1. How do I access the MariaDB database from a Windows GUI client? Tools like DBeaver or HeidiSQL installed on Windows can connect to MariaDB inside WSL. Use localhost as the host, port 3306, and the credentials you set in Phase 3.

2. Why is my disk IO slow? Crucial: Always store your project files inside the Linux filesystem (/home/your_username/...). Do not use /mnt/c/Users/.... Cross-OS file system access is very slow.

3. How do I debug Python code? In VS Code connected to WSL, install the Python extension. You can then add a launch.json configuration to attach the debugger to the running bench process or run specific scripts.

4. Can I use Docker instead? Yes, but Docker on Windows adds a layer of abstraction that can make debugging code harder. This "Metal on WSL" approach is often preferred for core backend development because the code runs directly in the OS you are interacting with.

5. What should I do if bench start fails to start the server? Check if ports 8000 (web) and 3306 (DB) are free. Run sudo netstat -tlnp | grep :8000 to check. Also, ensure Redis and MariaDB services are running with sudo systemctl status redis-server and sudo systemctl status mariadb. Restart WSL if needed.

6. How do I update ERPNext or my custom app? For ERPNext: bench update --pull (from bench root). For your app: Pull latest changes in your app repo, then bench --site dev.localhost migrate and bench build. Always backup your site first with bench --site dev.localhost backup.

7. What's the difference between code-first and UI-first DocType creation? UI-first is easier for beginners (design in browser, export JSON). Code-first is better for version control (write JSON/Python manually, then migrate). Use UI-first for prototyping, code-first for production features.

8. How do I handle multiple developers working on the same app? Use Git branches for features. Each developer should have their own WSL bench. For shared development, consider a central bench server or use Docker for consistent environments. Always pull before pushing and resolve conflicts carefully.

9. Why are my changes not showing up after bench build? Clear browser cache (Ctrl+F5) and check if assets are built correctly. Run bench clear-cache and restart the bench. Ensure your files are in the correct app directory and not overridden by site customizations.

10. How do I migrate from a Docker-based setup to WSL? Create a backup of your Docker site using bench --site yoursite backup --with-files. Copy the backup files (SQL and public/private files) to your WSL instance. Create a new site in WSL, then restore using bench --site newsite restore /path/to/backup.sql.gz --with-public-files /path/to/public.tar --with-private-files /path/to/private.tar. Finally, install your custom apps and run bench migrate.

11. What are some performance tips for development? Use bench start --concurrency 1 for lighter resource usage. Disable unnecessary apps on your dev site. Keep your WSL distro updated and avoid running too many services simultaneously. For large projects, consider increasing WSL memory allocation in .wslconfig.

12. How do I secure my development environment? Never commit secrets to Git. Use strong passwords for MariaDB. Limit WSL port exposure if needed. For production-like security, consider using separate VMs or cloud instances instead of local WSL for sensitive data.

Share This Article

About the Author

David Muraya is a Solutions Architect specializing in Python, FastAPI, and Cloud Infrastructure. He is passionate about building scalable, production-ready applications and sharing his knowledge with the developer community. You can connect with him on LinkedIn.

Related Blog Posts

Enjoyed this blog post? Check out these related posts!

How to Install ERPNext on Ubuntu 24 Using Bench

How to Install ERPNext on Ubuntu 24 Using Bench

A manual, production-ready guide using UV and Python 3.14

Read More...

How to Install ERPNext on Ubuntu with Docker

How to Install ERPNext on Ubuntu with Docker

A Complete Guide to Deploying ERPNext with Docker Compose, Nginx, and SSL

Read More...

Add Client-Side Search to Your Reflex Blog with MiniSearch

Add Client-Side Search to Your Reflex Blog with MiniSearch

How to Build Fast, Offline Search for Your Python Blog Using MiniSearch and Reflex

Read More...

Run Python Scripts for Free with GitHub Actions: A Complete Guide

Run Python Scripts for Free with GitHub Actions: A Complete Guide

Schedule and Automate Python Scripts Without Managing Servers or Cloud Bills

Read More...

Contact Me

Have a project in mind? Send me an email at hello@davidmuraya.com and let's bring your ideas to life. I am always available for exciting discussions.

© 2026 David Muraya. All rights reserved.