Merge branch 'main' into feature-cups

This commit is contained in:
martkaczmarek 2026-05-22 12:53:38 +02:00 committed by GitHub
commit 99db74d389
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 359 additions and 66 deletions

View file

@ -16,3 +16,4 @@ jobs:
stale-pr-message: This PR has been automatically marked as stale. You have a week to explain why you believe this is an error.
stale-issue-label: no-issue-activity
stale-pr-label: no-pr-activity
exempt-issue-labels: keep-open

View file

@ -46,6 +46,10 @@ services:
backend:
<<: *backend_defaults
platform: linux/amd64
environment:
GUNICORN_THREADS: ${GUNICORN_THREADS:-4}
GUNICORN_WORKERS: ${GUNICORN_WORKERS:-2}
GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-120}
frontend:
<<: *customizable_image

View file

@ -97,6 +97,7 @@ See [Automated Builds and Deployment](../03-production/06-automated-builds-and-d
| NODE_VERSION | Node.js version |
| WKHTMLTOPDF_VERSION | wkhtmltopdf version |
| INSTALL_PYCUPS | Set true to install pycups and its dependencies; see <https://docs.frappe.io/erpnext/print-settings> |
| INSTALL_CHROMIUM | Configure chromium installation, defaults to `true` - needed for Frappe Workbench version >15 |
| **bench only** | |
| DEBIAN_BASE | Debian base version for the bench image, defaults to `bookworm` |
| WKHTMLTOPDF_DISTRO | use the specified distro for debian package. Default is `bookworm` |

View file

@ -122,6 +122,16 @@ If your site is named `example.com` and you access it via that domain, no need t
---
## Backend (Gunicorn) Configuration
| Variable | Purpose | Default | When to Set / Allowed Values |
| :----------------- | :------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------- |
| `GUNICORN_WORKERS` | Number of worker processes handling web requests | `2` | Scale up for multi-core CPUs. Formula: `(2 x Cores) + 1` |
| `GUNICORN_THREADS` | Number of concurrent threads per worker process | `4` | Increase to handle more simultaneous I/O-bound requests without high memory cost |
| `GUNICORN_TIMEOUT` | Max time a worker can spend on a single request before restart | `120` | Increase if long-running reports or data imports time out |
---
## Frontend Nginx Configuration (inside the frontend container)
| Variable | Purpose | Default | Allowed Values |

View file

@ -0,0 +1,254 @@
---
title: Docker Development Setup
---
# Docker Development Setup
A complete guide for setting up a Frappe development environment on x86 and ARM based computers running UNIX based OSes by running containers directly and working inside them via the terminal. No VS Code Dev Containers extension needed.
> [!IMPORTANT]
> Devcontainers are the intended development setup for Frappe Framework but in case you don't want to use that method follow these instructions to use the CLI directly instead
---
## Prerequisites
- **Docker Desktop** (Applicable only for MacOS) — [download here](https://www.docker.com/products/docker-desktop/)
- **Git**
- A terminal (iTerm2, or the built-in Terminal.app)
### Docker Desktop Resource Allocation (Critical)
1. Open Docker Desktop → **Settings** → **Resources**
2. **Memory**: at least **6 GB** (8 GB recommended)
3. **CPUs**: at least **4**
4. **Disk image size**: at least **60 GB**
5. Click **Apply & Restart**
---
## Step 1 — Set ARM64 as Default Platform (ONLY FOR ARM BASED SYSTEMS)
```bash
export DOCKER_DEFAULT_PLATFORM=linux/arm64
```
Make it permanent:
```bash
echo 'export DOCKER_DEFAULT_PLATFORM=linux/arm64' >> ~/.zshrc
source ~/.zshrc
```
---
## Step 2 — Clone the Repo
```bash
git clone https://github.com/frappe/frappe_docker.git
cd frappe_docker
```
---
## Step 3 — Set Up the Dev Container Config
The `devcontainer-example/` folder contains a ready-made `docker-compose.yml` for development. Copy it into place:
```bash
cp -R devcontainer-example .devcontainer
```
This gives you `.devcontainer/docker-compose.yml` which defines all the services you need:
- `frappe` — the main development container (Debian, Python, Node, bench)
- `mariadb` — the database
- `redis-cache` — cache layer
- `redis-queue` — background job queue
---
## Step 4 — Add ARM64 Platform to All Services
Open `.devcontainer/docker-compose.yml` in any editor and add `platform: linux/arm64` to every service block. It should look like this:
```yaml
services:
frappe:
image: frappe/bench:latest
platform: linux/arm64
# ... rest of config
mariadb:
image: mariadb:10.8
platform: linux/arm64
# ...
redis-cache:
image: redis:6.2-alpine
platform: linux/arm64
# ...
redis-queue:
image: redis:6.2-alpine
platform: linux/arm64
# ...
```
> Without this, Docker may pull amd64 images and emulate them via Rosetta — things will work but be noticeably slower.
---
## Step 5 — Start the Containers
```bash
docker compose -f .devcontainer/docker-compose.yml up -d
```
Verify everything is running:
```bash
docker compose -f .devcontainer/docker-compose.yml ps
```
You should see all services with status `Up`.
In case you get any errors along the lines of,
```log
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint devcontainer-frappe-1 (44b337b68d100e914fab0ce446ed08d791cc73aaffb05cf47c347c00ff88f567): Bind for 0.0.0.0:9001 failed: port is already allocated
```
- Check if the port is being used by another service with `lsof -i :PORT`
> Usually on MacOS ports 8000 and 9000 are usually reserved for system use
- Go to line 60 and 61 under the `frappe` service and change the ports
Eg:
```
ports:
- 8001-8005:8001-8005
- 9002-9005:9002-9005
```
---
## Step 6 — Enter the Development Container
```bash
docker exec -e "TERM=xterm-256color" -w /workspace/development -it devcontainer-frappe-1 bash
```
> The container name is typically `devcontainer-frappe-1`. If it differs, check with `docker ps` and use the actual name shown.
You are now inside the container as the `frappe` user. All subsequent commands in this guide run **inside the container** unless noted otherwise.
---
## Step 7 — Initialize a Bench
```bash
bench init --skip-redis-config-generation --frappe-branch version-16 frappe-bench
cd frappe-bench
```
Use `version-16` for the latest stable release. Swap for `version-15` if needed.
This creates:
```
development/
└── frappe-bench/
├── apps/ ← All Frappe apps live here
├── sites/ ← Your sites (databases, uploaded files)
├── env/ ← Python virtualenv
├── logs/
└── Procfile
```
---
## Step 8 — Configure Service Hosts
Tell bench to use the containerised services (not localhost):
```bash
bench set-config -g db_host mariadb
bench set-config -g redis_cache redis://redis-cache:6379
bench set-config -g redis_queue redis://redis-queue:6379
bench set-config -g redis_socketio redis://redis-queue:6379
```
If any command fails, edit the file directly:
```bash
nano sites/common_site_config.json
```
Paste:
```json
{
"db_host": "mariadb",
"redis_cache": "redis://redis-cache:6379",
"redis_queue": "redis://redis-queue:6379",
"redis_socketio": "redis://redis-queue:6379"
}
```
---
## Step 9 — Fix the Procfile
Redis runs in separate containers, so remove it from Honcho's Procfile to avoid conflicts:
```bash
sudo sed -i '/redis/d' ./Procfile
```
---
## Step 10 — Create a Site
```bash
bench new-site \
--db-root-password 123 \
--admin-password admin \
--mariadb-user-host-login-scope=% \
development.localhost
```
- MariaDB root password: `123` (set in the docker-compose defaults)
- Admin password: `admin` (change this to whatever you want)
- Site name **must end in `.localhost`**
---
## Step 11 — Enable Developer Mode
```bash
bench --site development.localhost set-config developer_mode 1
bench --site development.localhost clear-cache
```
---
## Step 12 — Add development.localhost to /etc/hosts (on your Mac)
Run this **on your Mac** (not inside the container):
```bash
echo "127.0.0.1 development.localhost" | sudo tee -a /etc/hosts
```
---
## Step 13 — Start the Dev Server
```bash
bench build # (optional)
bench start
```
Open your browser at **http://development.localhost:8000**
Login: `Administrator` / `admin`

View file

@ -15,7 +15,7 @@ importers:
devDependencies:
vitepress:
specifier: 2.0.0-alpha.16
version: 2.0.0-alpha.16(postcss@8.5.8)
version: 2.0.0-alpha.16(postcss@8.5.15)
vitepress-sidebar:
specifier: 1.33.1
version: 1.33.1
@ -408,6 +408,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@vitejs/plugin-vue@6.0.5':
resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==}
@ -707,8 +708,8 @@ packages:
minisearch@7.2.0:
resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
nanoid@3.3.12:
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@ -739,8 +740,8 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
postcss@8.5.15:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
property-information@7.1.0:
@ -1214,7 +1215,7 @@ snapshots:
'@vue/shared': 3.5.30
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.8
postcss: 8.5.15
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.30':
@ -1491,7 +1492,7 @@ snapshots:
minisearch@7.2.0: {}
nanoid@3.3.11: {}
nanoid@3.3.12: {}
oniguruma-parser@0.12.1: {}
@ -1516,9 +1517,9 @@ snapshots:
picomatch@4.0.4: {}
postcss@8.5.8:
postcss@8.5.15:
dependencies:
nanoid: 3.3.11
nanoid: 3.3.12
picocolors: 1.1.1
source-map-js: 1.2.1
@ -1671,7 +1672,7 @@ snapshots:
esbuild: 0.27.4
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.8
postcss: 8.5.15
rollup: 4.59.0
tinyglobby: 0.2.15
optionalDependencies:
@ -1683,7 +1684,7 @@ snapshots:
gray-matter: 4.0.3
qsu: 1.10.4
vitepress@2.0.0-alpha.16(postcss@8.5.8):
vitepress@2.0.0-alpha.16(postcss@8.5.15):
dependencies:
'@docsearch/css': 4.6.0
'@docsearch/js': 4.6.0
@ -1705,7 +1706,7 @@ snapshots:
vite: 7.3.2
vue: 3.5.30
optionalDependencies:
postcss: 8.5.8
postcss: 8.5.15
transitivePeerDependencies:
- '@types/node'
- async-validator

View file

@ -1,6 +1,6 @@
# Reference: https://github.com/frappe/frappe_docker/blob/main/docs/02-setup/04-env-variables.md
ERPNEXT_VERSION=v16.16.0
ERPNEXT_VERSION=v16.19.1
DB_PASSWORD=123
@ -15,6 +15,17 @@ DB_PORT=
REDIS_CACHE=
REDIS_QUEUE=
# The number of threads per Gunicorn worker process for handling concurrent requests.
GUNICORN_THREADS=4
# The number of worker processes for handling requests.
# A typical formula is (2 x number of CPU cores) + 1.
GUNICORN_WORKERS=2
# Workers exceeding this timeout (in seconds) will be killed and restarted.
GUNICORN_TIMEOUT=120
# Only with HTTPS override
LETSENCRYPT_EMAIL=mail@example.com

View file

@ -4,6 +4,7 @@ LABEL author=frappé
ARG GIT_REPO=https://github.com/frappe/bench.git
ARG GIT_BRANCH=v5.x
ARG INSTALL_CHROMIUM=true
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
@ -23,8 +24,6 @@ RUN apt-get update \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
#Chromium
chromium-headless-shell \
# to work inside the container
locales \
build-essential \
@ -75,6 +74,11 @@ RUN apt-get update \
file \
# For MIME type detection
media-types \
# Chromium
&& if [ "$INSTALL_CHROMIUM" != "false" ]; then \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
chromium-headless-shell; \
fi \
&& rm -rf /var/lib/apt/lists/*
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \

View file

@ -8,6 +8,8 @@ COPY resources/core/nginx/security_headers.conf /etc/nginx/snippets/security_hea
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
ARG WKHTMLTOPDF_DISTRO=bookworm
ARG INSTALL_CHROMIUM=true
ARG NODE_VERSION=24.13.0
ENV NVM_DIR=/home/frappe/.nvm
ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
@ -27,8 +29,6 @@ RUN useradd -ms /bin/bash frappe \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
#Chromium
chromium-headless-shell \
# For backups
restic \
gpg \
@ -65,6 +65,11 @@ RUN useradd -ms /bin/bash frappe \
&& rm $downloaded_file \
# Install cups
&& if [ "${INSTALL_PYCUPS}" != "false" ]; then apt-get install --no-install-recommends -y libcups2-dev cups-client fi \
# Chromium
&& if [ "$INSTALL_CHROMIUM" != "false" ]; then \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
chromium-headless-shell; \
fi \
# Clean up
&& rm -rf /var/lib/apt/lists/* \
&& rm -fr /etc/nginx/sites-enabled/default \
@ -161,20 +166,12 @@ VOLUME [ \
USER root
# This entrypoint script link build assets of the image to the mounted sites volume at container initialization
COPY resources/core/main-entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
RUN chmod 755 /usr/local/bin/entrypoint.sh
COPY resources/core/start.sh /usr/local/bin/start.sh
RUN chmod 755 /usr/local/bin/start.sh
USER frappe
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD [ \
"/home/frappe/frappe-bench/env/bin/gunicorn", \
"--chdir=/home/frappe/frappe-bench/sites", \
"--bind=0.0.0.0:8000", \
"--threads=4", \
"--workers=2", \
"--worker-class=gthread", \
"--worker-tmp-dir=/dev/shm", \
"--timeout=120", \
"--preload", \
"frappe.app:application" \
]
CMD ["start.sh"]

View file

@ -47,20 +47,12 @@ VOLUME [ \
USER root
# This entrypoint script link build assets of the image to the mounted sites volume at container initialization
COPY resources/core/main-entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
RUN chmod 755 /usr/local/bin/entrypoint.sh
COPY resources/core/start.sh /usr/local/bin/start.sh
RUN chmod 755 /usr/local/bin/start.sh
USER frappe
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD [ \
"/home/frappe/frappe-bench/env/bin/gunicorn", \
"--chdir=/home/frappe/frappe-bench/sites", \
"--bind=0.0.0.0:8000", \
"--threads=4", \
"--workers=2", \
"--worker-class=gthread", \
"--worker-tmp-dir=/dev/shm", \
"--timeout=120", \
"--preload", \
"frappe.app:application" \
]
CMD ["start.sh"]

View file

@ -4,6 +4,8 @@ FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
ARG WKHTMLTOPDF_DISTRO=bookworm
ARG INSTALL_CHROMIUM=true
ARG NODE_VERSION=24.13.0
ENV NVM_DIR=/home/frappe/.nvm
ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
@ -23,8 +25,6 @@ RUN useradd -ms /bin/bash frappe \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
#Chromium
chromium-headless-shell \
# For backups
restic \
gpg \
@ -61,6 +61,11 @@ RUN useradd -ms /bin/bash frappe \
&& rm $downloaded_file \
# Install cups
&& if [ "${INSTALL_PYCUPS}" != "false" ]; then apt-get install --no-install-recommends -y libcups2-dev cups-client fi \
# Chromium
&& if [ "$INSTALL_CHROMIUM" != "false" ]; then \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
chromium-headless-shell; \
fi \
# Clean up
&& rm -rf /var/lib/apt/lists/* \
&& rm -fr /etc/nginx/sites-enabled/default \
@ -80,6 +85,7 @@ RUN useradd -ms /bin/bash frappe \
COPY resources/core/nginx/nginx-template.conf /templates/nginx/frappe.conf.template
COPY resources/core/nginx/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
COPY resources/core/nginx/security_headers.conf /etc/nginx/snippets/security_headers.conf
RUN chmod 755 /usr/local/bin/nginx-entrypoint.sh
FROM base AS build
@ -151,20 +157,12 @@ VOLUME [ \
USER root
# This entrypoint script link build assets of the image to the mounted sites volume at container initialization
COPY resources/core/main-entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
RUN chmod 755 /usr/local/bin/entrypoint.sh
COPY resources/core/start.sh /usr/local/bin/start.sh
RUN chmod 755 /usr/local/bin/start.sh
USER frappe
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD [ \
"/home/frappe/frappe-bench/env/bin/gunicorn", \
"--chdir=/home/frappe/frappe-bench/sites", \
"--bind=0.0.0.0:8000", \
"--threads=4", \
"--workers=2", \
"--worker-class=gthread", \
"--worker-tmp-dir=/dev/shm", \
"--timeout=120", \
"--preload", \
"frappe.app:application" \
]
CMD ["start.sh"]

16
pwd.yml
View file

@ -1,6 +1,6 @@
services:
backend:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:
@ -16,7 +16,7 @@ services:
MARIADB_ROOT_PASSWORD: admin
configurator:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:
@ -45,7 +45,7 @@ services:
- logs:/home/frappe/frappe-bench/logs
create-site:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:
@ -101,7 +101,7 @@ services:
- db-data:/var/lib/mysql
frontend:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
depends_on:
@ -127,7 +127,7 @@ services:
- "8080:8080"
queue-long:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:
@ -146,7 +146,7 @@ services:
FRAPPE_REDIS_QUEUE: redis://redis-queue:6379
queue-short:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:
@ -183,7 +183,7 @@ services:
condition: on-failure
scheduler:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:
@ -197,7 +197,7 @@ services:
- logs:/home/frappe/frappe-bench/logs
websocket:
image: frappe/erpnext:v16.16.0
image: frappe/erpnext:v16.19.1
networks:
- frappe_network
deploy:

20
resources/core/start.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
set -e
#Gunicorn defaults
GUNICORN_THREADS=${GUNICORN_THREADS:-4}
GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-120}
echo "Booting Gunicorn with $GUNICORN_WORKERS workers and $GUNICORN_THREADS threads..."
exec /home/frappe/frappe-bench/env/bin/gunicorn \
--chdir=/home/frappe/frappe-bench/sites \
--bind=0.0.0.0:8000 \
--threads="$GUNICORN_THREADS" \
--workers="$GUNICORN_WORKERS" \
--worker-class=gthread \
--worker-tmp-dir=/dev/shm \
--timeout="$GUNICORN_TIMEOUT" \
--preload \
frappe.app:application