[PR #29297] feat: introduce init container to automatically fix storage permissions #32361

Closed
opened 2026-02-21 20:51:15 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langgenius/dify/pull/29297

State: closed
Merged: Yes


Summary

This PR adds an init container that starts before the api/worker/beat containers.
The init container simply runs chown with root privileges on /app/api/storage and then exits.

Background

Since Dify 1.10, the api/worker/beat containers now run as uid:1001. While this improves security, it has negatively affected the user experience in some areas, especially in Linux-based environments:

  • During the initialization process after a fresh deployment, permission errors have started occurring with the storage.
  • After upgrading, workflows may fail because files can’t be written.

Of course, users can manually fix these issues by running chown command as stated in the release note, but ideally, these should be handled transparently by some automated mechanism, so users don’t have to deal with it themselves.

Ideas

With the following changes, we can solve the storage permission problem while maintaining the same level of security:

  • Add an init container that simply runs chown with root privileges and then exits.
  • Make the api/worker/beat containers depend_on the init container, so they only start after the init container reaches service_completed_successfully state.
  • We won’t change the user that runs the api/worker/beat containers.

Additional Notes

  • The chown command is run by root in the init container, but the container exits immediately after the command completes.
  • Of course, the api/worker/beat containers continue to run as the non-root user (uid:1001).
  • Since there’s no point in recursively running chown on all files every time we do docker compose up, chown is only run once during the lifecycle, controlled with a flag file under ./volumes/app/storage.
  • The downside is that the number of containers increases.

Screenshots

Before

$ docker compose up -d
...

$ docker compose ps 
NAME                     IMAGE                                       COMMAND                  SERVICE         CREATED          STATUS                             PORTS
docker-api-1             langgenius/dify-api:1.10.1-fix.1            "/bin/bash /entrypoi…"   api             12 seconds ago   Up 6 seconds                       5001/tcp
docker-db_postgres-1     postgres:15-alpine                          "docker-entrypoint.s…"   db_postgres     13 seconds ago   Up 11 seconds (healthy)            5432/tcp
docker-nginx-1           nginx:latest                                "sh -c 'cp /docker-e…"   nginx           12 seconds ago   Up 6 seconds                       0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:8888->80/tcp, :::8888->80/tcp
docker-plugin_daemon-1   langgenius/dify-plugin-daemon:0.4.1-local   "/bin/bash -c /app/e…"   plugin_daemon   12 seconds ago   Up 7 seconds                       0.0.0.0:5003->5003/tcp, :::5003->5003/tcp
docker-redis-1           redis:6-alpine                              "docker-entrypoint.s…"   redis           13 seconds ago   Up 11 seconds (health: starting)   6379/tcp
docker-sandbox-1         langgenius/dify-sandbox:0.2.12              "/main"                  sandbox         13 seconds ago   Up 11 seconds (health: starting)   
docker-ssrf_proxy-1      ubuntu/squid:latest                         "sh -c 'cp /docker-e…"   ssrf_proxy      13 seconds ago   Up 11 seconds                      3128/tcp
docker-weaviate-1        semitechnologies/weaviate:1.27.0            "/bin/weaviate --hos…"   weaviate        13 seconds ago   Up 11 seconds                      
docker-web-1             langgenius/dify-web:1.10.1-fix.1            "/bin/sh ./entrypoin…"   web             13 seconds ago   Up 11 seconds                      3000/tcp
docker-worker-1          langgenius/dify-api:1.10.1-fix.1            "/bin/bash /entrypoi…"   worker          12 seconds ago   Up 6 seconds                       5001/tcp
docker-worker_beat-1     langgenius/dify-api:1.10.1-fix.1            "/bin/bash /entrypoi…"   worker_beat     12 seconds ago   Up 7 seconds                       5001/tcp

$ ls -l ./volumes/app
total 0
drwxr-xr-x. 2 root root 6 Dec  9 01:31 storage   ⚠️⚠️⚠️ The owner is root so uid:1001 doesn't have write permission.

$ ls -la ./volumes/app/storage
total 0
drwxr-xr-x. 2 root root  6 Dec  9 01:31 .
drwxr-xr-x. 3 root root 21 Dec  9 01:31 ..

After

$ docker compose up -d
...

$ docker compose ps -a
NAME                        IMAGE                                       COMMAND                   SERVICE            CREATED          STATUS                             PORTS
docker-api-1                langgenius/dify-api:1.10.1-fix.1            "/bin/bash /entrypoi…"    api                18 seconds ago   Up 14 seconds                      5001/tcp
docker-db_postgres-1        postgres:15-alpine                          "docker-entrypoint.s…"    db_postgres        18 seconds ago   Up 17 seconds (healthy)            5432/tcp
✅✅✅ 👇 Init container
docker-init_permissions-1   busybox:latest                              "sh -c 'FLAG_FILE=\"/…"   init_permissions   18 seconds ago   Exited (0) 17 seconds ago          
docker-nginx-1              nginx:latest                                "sh -c 'cp /docker-e…"    nginx              18 seconds ago   Up 13 seconds                      0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:8888->80/tcp, :::8888->80/tcp
docker-plugin_daemon-1      langgenius/dify-plugin-daemon:0.4.1-local   "/bin/bash -c /app/e…"    plugin_daemon      18 seconds ago   Up 14 seconds                      0.0.0.0:5003->5003/tcp, :::5003->5003/tcp
docker-redis-1              redis:6-alpine                              "docker-entrypoint.s…"    redis              19 seconds ago   Up 17 seconds (health: starting)   6379/tcp
docker-sandbox-1            langgenius/dify-sandbox:0.2.12              "/main"                   sandbox            18 seconds ago   Up 17 seconds (health: starting)   
docker-ssrf_proxy-1         ubuntu/squid:latest                         "sh -c 'cp /docker-e…"    ssrf_proxy         19 seconds ago   Up 17 seconds                      3128/tcp
docker-weaviate-1           semitechnologies/weaviate:1.27.0            "/bin/weaviate --hos…"    weaviate           19 seconds ago   Up 17 seconds                      
docker-web-1                langgenius/dify-web:1.10.1-fix.1            "/bin/sh ./entrypoin…"    web                19 seconds ago   Up 17 seconds                      3000/tcp
docker-worker-1             langgenius/dify-api:1.10.1-fix.1            "/bin/bash /entrypoi…"    worker             18 seconds ago   Up 14 seconds                      5001/tcp
docker-worker_beat-1        langgenius/dify-api:1.10.1-fix.1            "/bin/bash /entrypoi…"    worker_beat        18 seconds ago   Up 14 seconds                      5001/tcp

$ docker compose logs init_permissions
init_permissions-1  | Initializing permissions for /app/api/storage   ✅✅✅ Initializing
init_permissions-1  | Permissions initialized. Exiting.

$ ls -l ./volumes/app/
total 0
drwxr-xr-x. 2 1001 1001 31 Dec  9 01:32 storage   ✅✅✅ The owner is 1001

$ ls -la ./volumes/app/storage/
total 0
drwxr-xr-x. 2 1001 1001 31 Dec  9 01:32 .
drwxr-xr-x. 3 root root 21 Dec  9 01:32 ..
-rw-r--r--. 1 root root  0 Dec  9 01:32 .init_permissions   ✅✅✅ Flag file

$ docker compose down; docker compose up -d   ✅✅✅ Try restarting
...

$ docker compose logs init_permissions
init_permissions-1  | Permissions already initialized. Exiting.    ✅✅✅ It's second time, so chown is skipped by the flag file.

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran dev/reformat(backend) and cd web && npx lint-staged(frontend) to appease the lint gods
**Original Pull Request:** https://github.com/langgenius/dify/pull/29297 **State:** closed **Merged:** Yes --- ## Summary This PR adds an init container that starts before the api/worker/beat containers. The init container simply runs `chown` with root privileges on `/app/api/storage` and then exits. ### Background Since Dify 1.10, the api/worker/beat containers now run as uid:1001. While this improves security, it has negatively affected the user experience in some areas, especially in Linux-based environments: - During the initialization process after a fresh deployment, permission errors have started occurring with the storage. - After upgrading, workflows may fail because files can’t be written. Of course, users can manually fix these issues by running `chown` command as stated in the release note, but ideally, these should be handled transparently by some automated mechanism, so users don’t have to deal with it themselves. - Closes #29149 - Related to - #29162 - #29098 - #29071 - #29062 - #29055 - #28792 - #28791 - https://forum.dify.ai/t/v1-10-1-fix-1/285 ### Ideas With the following changes, we can solve the storage permission problem while maintaining the same level of security: - Add an init container that simply runs `chown` with root privileges and then exits. - Make the api/worker/beat containers `depend_on` the init container, so they only start after the init container reaches `service_completed_successfully` state. - We won’t change the user that runs the api/worker/beat containers. ### Additional Notes - The `chown` command is run by root in the init container, but the container exits immediately after the command completes. - Of course, the api/worker/beat containers continue to run as the non-root user (uid:1001). - Since there’s no point in recursively running `chown` on all files every time we do `docker compose up`, `chown` is only run once during the lifecycle, controlled with a flag file under `./volumes/app/storage`. - The downside is that the number of containers increases. ## Screenshots ### Before ```bash $ docker compose up -d ... $ docker compose ps NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS docker-api-1 langgenius/dify-api:1.10.1-fix.1 "/bin/bash /entrypoi…" api 12 seconds ago Up 6 seconds 5001/tcp docker-db_postgres-1 postgres:15-alpine "docker-entrypoint.s…" db_postgres 13 seconds ago Up 11 seconds (healthy) 5432/tcp docker-nginx-1 nginx:latest "sh -c 'cp /docker-e…" nginx 12 seconds ago Up 6 seconds 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:8888->80/tcp, :::8888->80/tcp docker-plugin_daemon-1 langgenius/dify-plugin-daemon:0.4.1-local "/bin/bash -c /app/e…" plugin_daemon 12 seconds ago Up 7 seconds 0.0.0.0:5003->5003/tcp, :::5003->5003/tcp docker-redis-1 redis:6-alpine "docker-entrypoint.s…" redis 13 seconds ago Up 11 seconds (health: starting) 6379/tcp docker-sandbox-1 langgenius/dify-sandbox:0.2.12 "/main" sandbox 13 seconds ago Up 11 seconds (health: starting) docker-ssrf_proxy-1 ubuntu/squid:latest "sh -c 'cp /docker-e…" ssrf_proxy 13 seconds ago Up 11 seconds 3128/tcp docker-weaviate-1 semitechnologies/weaviate:1.27.0 "/bin/weaviate --hos…" weaviate 13 seconds ago Up 11 seconds docker-web-1 langgenius/dify-web:1.10.1-fix.1 "/bin/sh ./entrypoin…" web 13 seconds ago Up 11 seconds 3000/tcp docker-worker-1 langgenius/dify-api:1.10.1-fix.1 "/bin/bash /entrypoi…" worker 12 seconds ago Up 6 seconds 5001/tcp docker-worker_beat-1 langgenius/dify-api:1.10.1-fix.1 "/bin/bash /entrypoi…" worker_beat 12 seconds ago Up 7 seconds 5001/tcp $ ls -l ./volumes/app total 0 drwxr-xr-x. 2 root root 6 Dec 9 01:31 storage ⚠️⚠️⚠️ The owner is root so uid:1001 doesn't have write permission. $ ls -la ./volumes/app/storage total 0 drwxr-xr-x. 2 root root 6 Dec 9 01:31 . drwxr-xr-x. 3 root root 21 Dec 9 01:31 .. ``` ### After ```bash $ docker compose up -d ... $ docker compose ps -a NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS docker-api-1 langgenius/dify-api:1.10.1-fix.1 "/bin/bash /entrypoi…" api 18 seconds ago Up 14 seconds 5001/tcp docker-db_postgres-1 postgres:15-alpine "docker-entrypoint.s…" db_postgres 18 seconds ago Up 17 seconds (healthy) 5432/tcp ✅✅✅ 👇 Init container docker-init_permissions-1 busybox:latest "sh -c 'FLAG_FILE=\"/…" init_permissions 18 seconds ago Exited (0) 17 seconds ago docker-nginx-1 nginx:latest "sh -c 'cp /docker-e…" nginx 18 seconds ago Up 13 seconds 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:8888->80/tcp, :::8888->80/tcp docker-plugin_daemon-1 langgenius/dify-plugin-daemon:0.4.1-local "/bin/bash -c /app/e…" plugin_daemon 18 seconds ago Up 14 seconds 0.0.0.0:5003->5003/tcp, :::5003->5003/tcp docker-redis-1 redis:6-alpine "docker-entrypoint.s…" redis 19 seconds ago Up 17 seconds (health: starting) 6379/tcp docker-sandbox-1 langgenius/dify-sandbox:0.2.12 "/main" sandbox 18 seconds ago Up 17 seconds (health: starting) docker-ssrf_proxy-1 ubuntu/squid:latest "sh -c 'cp /docker-e…" ssrf_proxy 19 seconds ago Up 17 seconds 3128/tcp docker-weaviate-1 semitechnologies/weaviate:1.27.0 "/bin/weaviate --hos…" weaviate 19 seconds ago Up 17 seconds docker-web-1 langgenius/dify-web:1.10.1-fix.1 "/bin/sh ./entrypoin…" web 19 seconds ago Up 17 seconds 3000/tcp docker-worker-1 langgenius/dify-api:1.10.1-fix.1 "/bin/bash /entrypoi…" worker 18 seconds ago Up 14 seconds 5001/tcp docker-worker_beat-1 langgenius/dify-api:1.10.1-fix.1 "/bin/bash /entrypoi…" worker_beat 18 seconds ago Up 14 seconds 5001/tcp $ docker compose logs init_permissions init_permissions-1 | Initializing permissions for /app/api/storage ✅✅✅ Initializing init_permissions-1 | Permissions initialized. Exiting. $ ls -l ./volumes/app/ total 0 drwxr-xr-x. 2 1001 1001 31 Dec 9 01:32 storage ✅✅✅ The owner is 1001 $ ls -la ./volumes/app/storage/ total 0 drwxr-xr-x. 2 1001 1001 31 Dec 9 01:32 . drwxr-xr-x. 3 root root 21 Dec 9 01:32 .. -rw-r--r--. 1 root root 0 Dec 9 01:32 .init_permissions ✅✅✅ Flag file $ docker compose down; docker compose up -d ✅✅✅ Try restarting ... $ docker compose logs init_permissions init_permissions-1 | Permissions already initialized. Exiting. ✅✅✅ It's second time, so chown is skipped by the flag file. ``` ## Checklist - [ ] This change requires a documentation update, included: [Dify Document](https://github.com/langgenius/dify-docs) - [x] I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!) - [ ] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change. - [ ] I've updated the documentation accordingly. - [ ] I ran `dev/reformat`(backend) and `cd web && npx lint-staged`(frontend) to appease the lint gods
yindo added the pull-request label 2026-02-21 20:51:15 -05:00
yindo closed this issue 2026-02-21 20:51:15 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#32361