Running Odoo on a multi-node Docker Swarm raises one storage question: where does the filestore live? Swarm's default volumes are local to a node. If an Odoo task moves to another node, its attachments stay behind. This guide covers putting the Odoo filestore on NFS, which is the most common answer, along with the other options that are still current: pinning to a node, CSI cluster volumes and object storage. It also lists the NFS details that matter for Odoo.
What Odoo stores on disk
Odoo keeps its data in two places:
- PostgreSQL holds records, settings and users.
data_dir(/var/lib/odooin the official image) holdsfilestore/<database>, meaning attachments, images and generated asset bundles, andsessions/, the login sessions.
If the filestore is missing, records load but images, PDFs and attachments fail. In some cases the web client's asset bundles break too. If the sessions folder isn't shared between replicas, users get logged out at random. So the filestore and the database must always be backed up, moved and restored together.
Odoo shared storage options in Swarm
| Option | Task can move nodes | Multiple replicas | Effort |
|---|---|---|---|
| Local volume, pinned to one node | No | Only on that node | Lowest |
| NFS (host mount or volume driver) | Yes | Yes | Low: one NFS server or a managed file service |
| CSI cluster volume | Yes | Depends on the plugin | Medium: few plugins are packaged for Swarm |
| Object storage for attachments | Yes | Yes | Medium: needs an extra Odoo module, sessions still need a disk |
You will still see REX-Ray, Flocker, Convoy, Portworx and GlusterFS in older Swarm tutorials. REX-Ray has had no release since 2019, Flocker's code has not changed since 2017, and Convoy's repository is archived. Portworx has deprecated its Docker Swarm support. Red Hat Gluster Storage reached end of life at the end of 2024. Don't build anything new on them.
Option 1: pin Odoo to one node
For a single instance with no failover requirement, a local volume plus a placement constraint is enough:
docker node update --label-add odoo.data=true <node-name>
deploy:
placement:
constraints:
- node.labels.odoo.data == true
If that node fails, Odoo stays down until it comes back. The full stack is in deploy an Odoo docker stack.
Option 2: Odoo filestore on NFS
NFS is the practical default. Easy Instance uses shared storage like this: each instance's data directory, config and addons are bind-mounted from storage that every eligible node can reach. A task can then start wherever the scheduler places it. There are two ways to wire it up.
Mount NFS on every node and bind-mount it
Export a directory from the NFS server (or use your cloud's managed NFS service):
/srv/odoo 192.168.50.0/24(rw,sync,no_subtree_check)Mount it at the same path on every Swarm node that may run Odoo:
nfs.example.internal:/srv/odoo /mnt/odoo nfs4 rw,hard,noatime,nfsvers=4.1,_netdev 0 0 volumes:
- /mnt/odoo/shop1:/var/lib/odoo
This is easy to debug, because ls /mnt/odoo on any node shows the real files. A node without the mount must not run Odoo, so add a node label such as storage.odoo=true to the nodes that have it and constrain on that label.
Let Docker mount NFS through the local volume driver
volumes:
odoo-data:
driver: local
driver_opts:
type: nfs
o: "addr=nfs.example.internal,nfsvers=4.1,rw,hard,noatime"
device: ":/srv/odoo/shop1"No changes are needed on the hosts beyond the NFS client package (nfs-common on Debian and Ubuntu). One catch: Docker creates the volume separately on each node the first time a task uses it there, and never updates it afterwards. If you change the options, you have to run docker volume rm on every node that already has the volume.
NFS details that matter for Odoo
- Use
hardmounts. Asoftmount returns I/O errors when the server is slow, and Odoo can end up with half-written attachments. - File ownership. The official image runs Odoo as a non-root user. Check the user ID with
docker run --rm odoo:19.0 idand give the export directory to that ID. Check again after upgrading the image, because the ID has changed between Odoo releases. - "Directory not empty" on delete. When a file that a process still has open is deleted, NFS renames it to
.nfsXXXXuntil the process closes it. Stop the Odoo service before deleting an instance's folder, and retry after a few seconds. - Stale lock files. An operation interrupted on NFS, such as a git pull into an addons folder, can leave lock files behind. Remove them before retrying.
- Concurrency is fine. Filestore files are named by their content hash, so several replicas can write to the same filestore safely. That is what makes scaling Odoo to several replicas possible.
- Keep PostgreSQL off NFS. Run the database on local disk, or on a dedicated database server as Easy Instance does, and share only
data_dir.
Option 3: CSI cluster volumes
Since Docker Engine 23, Swarm can use CSI storage plugins as cluster volumes. The scheduler knows where each volume is available and places tasks to match. The plugin must be installed on every manager and on the nodes that use the volume. Volumes are created with cluster-specific flags and mounted with type=cluster:
docker volume create \
--driver <csi-plugin> \
--type mount \
--sharing all \
--scope multi \
odoo-shop1-data
docker service create \
--name shop1 \
--mount type=cluster,src=odoo-shop1-data,dst=/var/lib/odoo \
odoo:19.0
The limits are real. Most CSI drivers ship for Kubernetes only. Snapshots, cloning and volume expansion are not supported. Plugins aren't rolled out to new nodes automatically. Test failover before you rely on it for production data.
Option 4: attachments in object storage
Community modules, for example from the OCA storage repository, can store Odoo attachments in S3-compatible object storage instead of the filestore. That removes most of the shared-disk traffic. It is a change at the Odoo level: install and test it per Odoo version. Sessions and generated assets still need a data_dir, so this works alongside a small shared or local volume, not as a full replacement.
Back up the database and filestore together
Restoring a database without the matching filestore gives you records whose attachments are missing. Odoo's own backup format (a zip with a SQL dump plus the filestore) keeps them in sync. On NFS, snapshot the share at the same time as the database dump. See Odoo backup for how scheduled backups and restores work on Easy Instance.
Storage already handled
On Easy Instance the shared filestore, daily backups and restores are part of the service. Read how Easy Instance runs Odoo on Docker Swarm for the full setup. You can try it free for 10 days with no credit card. Extra storage is priced on the pricing page.