Odoo Filestore on NFS: Shared Storage for Docker Swarm

Put the Odoo filestore on NFS so Swarm tasks can move nodes: options compared, mount settings, Odoo-specific pitfalls and current CSI cluster volumes.

Updated . 5 min read

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/odoo in the official image) holds filestore/<database>, meaning attachments, images and generated asset bundles, and sessions/, 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

OptionTask can move nodesMultiple replicasEffort
Local volume, pinned to one nodeNoOnly on that nodeLowest
NFS (host mount or volume driver)YesYesLow: one NFS server or a managed file service
CSI cluster volumeYesDepends on the pluginMedium: few plugins are packaged for Swarm
Object storage for attachmentsYesYesMedium: 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):

/etc/exports on the NFS server
/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:

/etc/fstab on each node
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

volume definition in the stack file
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 hard mounts. A soft mount 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 id and 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 .nfsXXXX until 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.

Rather not run the server yourself?

Get managed Odoo hosting with backups, SSL, monitoring and updates handled for you. Free for 10 days.