Summary: If a Node, Alpine or n8n container throws SQLITE_READONLY: attempt to write a readonly database after you mounted a volume, the cause is almost always ownership. The process inside the container runs as one user ID, and the mounted folder belongs to another. Check the ID, check the folder owner, change the owner to match, and do not reach for chmod 777.
What the error actually means
SQLite reports SQLITE_READONLY when it cannot write. That sounds like a problem with the database file, but SQLite writes more than one file. Depending on journal mode it also creates a rollback journal or -wal and -shm files next to the database. So the folder has to be writable, not only the file.
That is why the error survives a fix that looks correct. You make database.sqlite writable, the folder is still owned by root, and the first write still fails.
Cause 1: the user ID does not match the folder owner
This is the usual one. The official Node images ship with a user called node, ID 1000. Whether your container runs as that user depends on the image and your configuration. Plain Node images run as root unless a USER line or a compose user: setting says otherwise. The official n8n image runs as the node user, and keeps its data in /home/node/.n8n.
Now add a bind mount such as ./n8n-data:/home/node/.n8n. If Docker creates ./n8n-data on the host as root, the container user with ID 1000 cannot write to it. SQLite opens the file read-only and gives you the error above.
Named volumes behave differently. When you mount an empty named volume onto a folder that already exists in the image, Docker copies the image’s contents and ownership into it on first use. That is why the n8n_data named volume in the project’s own examples tends to work and a hand-made bind mount often does not.
Check it in two minutes
- Find the user ID. Run
docker exec -it <container> id. Note theuidandgid. - Find the folder owner. Run
docker exec -it <container> ls -ln /home/node/.n8n(or your own mount path). The-nflag shows numeric IDs, which is what matters, not names. - Test a write. Run
docker exec -it <container> touch /home/node/.n8n/test. If that says permission denied or read-only file system, you have confirmed it is the mount and not SQLite.
The wording of the failure helps. “Permission denied” points to ownership. “Read-only file system” points to a :ro flag or a read-only host mount.
The fix
Change the owner on the host to the ID the container runs as: sudo chown -R 1000:1000 ./n8n-data. Restart the container. This is the cleanest fix and leaves permissions tight.
Or set the user in compose to match the folder you already have, for example user: "1000:1000", when the host folder is owned by that ID. Make sure the ID you pick can read everything else the container needs.
Or switch to a named volume and let Docker handle ownership, if you do not need to browse the files from the host. For anything you care about, take a backup first either way.
Other causes worth ruling out
- A
:roflag on the mount. Look for:roat the end of the volume line. SQLite cannot write through it. - Mounting only the database file. If you mount a single file, the container cannot create the journal beside it. Mount the folder.
- SELinux on Fedora, RHEL and similar. A bind mount may need the
:zor:Zlabel option before the container can write. Ownership will look correct and writes will still fail. - Network shares. SQLite’s own documentation warns about file locking on network filesystems such as NFS. If your data folder sits on one, expect locking trouble even once permissions are right.
Why not chmod 777
It works, which is the problem. Every account on the host can then read and modify the database. For n8n that folder also holds the config file with your encryption key, which protects stored credentials. Match the owner to the container user and leave the mode alone.
When this is not your problem
A readonly error is a permissions issue and the data is normally intact. If you see “database disk image is malformed” instead, that is corruption, which has a different cause and recovery path. I wrote up that case separately in n8n SQLite corruption in Docker.
Frequently asked questions
Why does SQLite say the database is readonly when the file is writable?
SQLite needs to write more than the database file. It creates journal or write-ahead log files in the same folder, so the folder must be writable by the user running the process too. A writable file inside a folder the container user cannot write to still fails.
Should I just chmod 777 the volume?
No. It makes the error go away by letting every user on the host write to your data. Set the owner to the user ID the container actually runs as instead.
What user ID does a Node Alpine container run as?
The official Node images include a user named node with ID 1000, but the container runs as root unless the image or your compose file sets a user. The official n8n image runs as the node user, so its data folder must be writable by ID 1000.
Is this the same as SQLite database corruption?
No. A readonly error is a permissions or mount problem and the data is usually fine. Corruption is damaged data, with different error messages and a different recovery path.
When this matters - and when it doesn’t
This applies if: you self-host n8n or another Node tool in Docker with SQLite and a mounted volume, and writes started failing after a move, an upgrade or a fresh bind mount.
Skip this if: you use a hosted service where someone else runs the container. There is nothing for you to fix, and self-hosting is not worth taking on just to save a subscription.
Get the next one before it is published.
I post the prompts, workflows and checklists behind these articles in the community first, and answer questions there myself.
Join the community