Need shared storage between OpenStack instances, but Manila isn't available in your deployment? You're not out of luck. With a Cinder volume and a plain Ubuntu VM running NFS, you can get a solid shared filesystem up and running in a few minutes.
In this post I'll walk through the whole setup, from creating a dedicated project to mounting the share on client VMs.
TL;DR
- Create a dedicated project and user (
shares) - Spin up an Ubuntu 22.04 VM as the NFS server
- Attach a Cinder volume, format it, and mount it on
/srv/nfs - Export
/srv/nfs/shareover NFSv4 (TCP 2049) - Lock it down with Security Groups
- Mount it from any client VM with
nfs-common
The architecture
OpenStack │ ├── Project: shares │ ├── User: shares │ ├── NFS Server VM (Ubuntu 22.04) │ │ ├── System Disk │ │ ├── Cinder Volume │ │ │ └── /srv/nfs/share │ │ └── NFS Server │ │ └── TCP 2049 │ │ │ └── Client VM(s) │ └── /mnt/shared-storage │ └── Neutron Network └── Private connectivity between instances The idea is simple: Cinder gives us persistent block storage, NFS turns it into shared file storage. The size of the Cinder volume defines the capacity of the share.
⚠️ Heads up: this is not a Manila replacement feature-for-feature. There are no share quotas, and a single NFS server is a single point of failure. More on that at the end.
Prerequisites
- Access to an OpenStack deployment with the
openstackCLI installed - Permissions to create projects and users (admin) for step 1
- The
memberrole available in your deployment - Instances that can reach each other over a Neutron network
1. Create a dedicated project and user
I like keeping storage infrastructure separate from general-purpose workloads. A dedicated project makes resource management much easier.
# Create the project openstack project create \ --description "Dedicated project for shared storages" \ shares # Create the user (you'll be prompted for a password) openstack user create \ --description "User for managing storage instances" \ --password-prompt \ shares # Give the user the member role in the project openstack role add \ --project shares \ --user shares \ member # Set the default project openstack user set \ --project shares \ shares Verify everything:
openstack project show shares openstack user show shares openstack role assignment list \ --project shares \ --user shares \ --names The
memberrole must exist in your deployment, and your OpenStack policies must allow the operations we're about to run.
2. Create the RC file
Create shares-openrc.sh with the credentials for the new user:
#!/usr/bin/env bash export OS_AUTH_URL="https://<KEYSTONE_ENDPOINT>:5000/v3" export OS_IDENTITY_API_VERSION=3 export OS_AUTH_TYPE="password" export OS_PROJECT_NAME="shares" export OS_PROJECT_DOMAIN_NAME="Default" export OS_USERNAME="shares" export OS_USER_DOMAIN_NAME="Default" export OS_PASSWORD="<USER_PASSWORD>" Adjust the Keystone endpoint, port, and domains to match your environment. Then lock the file down and load it:
chmod 600 shares-openrc.sh source shares-openrc.sh # Sanity check openstack token issue openstack project show shares 🔐 This file contains a plaintext password. Protect it, or use a supported alternative authentication method (such as application credentials) where available.
3. Upload a cloud image
Grab a qcow2 cloud image for your architecture, for example:
- Ubuntu Server 22.04 LTS: https://cloud-images.ubuntu.com/jammy/
- CentOS Stream 9: https://cloud.centos.org/centos/9-stream/
This guide uses Ubuntu 22.04. Upload it to Glance:
source shares-openrc.sh openstack image create "Ubuntu-22.04" \ --file jammy-server-cloudimg-amd64.img \ --disk-format qcow2 \ --container-format bare \ --private Replace the filename with the one you actually downloaded, then verify:
openstack image list openstack image show Ubuntu-22.04 4. Create an SSH key pair
mkdir -p ~/.ssh chmod 700 ~/.ssh openstack keypair create \ --private-key ~/.ssh/shares-key.pem \ shares-key chmod 600 ~/.ssh/shares-key.pem Verify it:
openstack keypair show shares-key openstack keypair list Keep that private key safe, since you'll need it to SSH into the server.
5. Launch the NFS server instance
First, check that everything you need exists:
openstack image list openstack flavor list openstack network list openstack keypair list openstack security group list Then create the VM:
openstack server create \ --image Ubuntu-22.04 \ --flavor <FLAVOR_NAME> \ --network <NETWORK_NAME> \ --key-name shares-key \ --security-group <SECURITY_GROUP_NAME> \ nfs-server Wait for the status to become ACTIVE, and grab its addresses:
openstack server show nfs-server \ -c status \ -c addresses 6. Floating IP (only if you need it)
Instances on the same private network can already talk to each other, so you don't necessarily need a Floating IP. You'll need one if you want to reach the server from an external or administrative network (for SSH, for example).
# Find the external network openstack network list --external # Allocate a Floating IP openstack floating ip create <EXTERNAL_NETWORK_NAME> # Associate it openstack server add floating ip \ nfs-server \ <FLOATING_IP_ADDRESS> # Verify openstack server show nfs-server openstack floating ip list 💡 For NFS traffic between private instances, always prefer the server's private IP. Don't expose storage services through a Floating IP unless you really have to.
7. Open SSH in the Security Group
Find the port and Security Group attached to the server:
openstack port list --server nfs-server openstack port show <PORT_ID> openstack security group rule list <SECURITY_GROUP_ID> If there's no SSH rule, add one restricted to your admin network:
openstack security group rule create \ --ingress \ --protocol tcp \ --dst-port 22 \ --remote-ip <ADMIN_CIDR> \ <SECURITY_GROUP_ID> For a single admin host, use a /32 such as 192.168.10.15/32.
🚫 Avoid
0.0.0.0/0for SSH unless you have a specific, justified reason.
Now connect (the default Ubuntu user is ubuntu):
# Via private IP ssh -i ~/.ssh/shares-key.pem ubuntu@<NFS_SERVER_PRIVATE_IP> # Or via Floating IP ssh -i ~/.ssh/shares-key.pem ubuntu@<FLOATING_IP_ADDRESS> From here on, most of the work happens inside the VM.
8. Create and attach the Cinder volume
This is where the storage capacity is defined. Unlike Manila, where you pick the size at share creation, here the Cinder volume size is the capacity.
openstack volume create \ --size 100 \ nfs-share-volume Change 100 to whatever you need (in GB), then check it:
openstack volume show nfs-share-volume openstack volume list Wait for the available status, then attach it:
openstack server add volume \ nfs-server \ nfs-share-volume Verify the attachment:
openstack server show nfs-server openstack volume show nfs-share-volume 9. Format and mount the volume (inside the VM)
⚠️ Formatting destroys all data on the target device. Double-check the device name, and never format the OS disk.
Identify the new disk:
lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINTS In this example it's /dev/vdb, but yours may differ.
Format it with one filesystem of your choice.
ext4:
sudo mkfs.ext4 /dev/vdb or XFS:
sudo apt update sudo apt install -y xfsprogs sudo mkfs.xfs /dev/vdb Create the mount point and mount it:
sudo mkdir -p /srv/nfs sudo mount /dev/vdb /srv/nfs df -h /srv/nfs lsblk -f Make it persistent
Grab the filesystem UUID:
sudo blkid /dev/vdb /dev/vdb: UUID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" TYPE="ext4" Add an entry to /etc/fstab:
sudo nano /etc/fstab For ext4:
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /srv/nfs ext4 defaults,nofail 0 2 For XFS:
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /srv/nfs xfs defaults,nofail 0 0 Test it without rebooting:
sudo mount -a findmnt /srv/nfs 🧠
nofaillets the VM boot even if the volume is missing. In production, make sure that failure can't cause NFS to export the underlying system filesystem by accident.
10. Install and configure the NFS server
sudo apt update sudo apt install -y nfs-kernel-server sudo systemctl enable --now nfs-kernel-server sudo systemctl status nfs-kernel-server Create the shared directory on the mounted volume:
sudo mkdir -p /srv/nfs/share For a first connectivity test, you can use broad permissions:
sudo chown root:root /srv/nfs/share sudo chmod 0777 /srv/nfs/share ⚠️ Don't keep
0777in production. Set ownership and permissions based on your workloads and their UID/GID requirements.
Check that the directory really lives on the Cinder volume:
df -h /srv/nfs/share Configure the export
sudo nano /etc/exports Add (replacing <CLIENT_CIDR> with the subnet allowed to access the share):
/srv/nfs/share <CLIENT_CIDR>(rw,sync,no_subtree_check,root_squash) For example:
/srv/nfs/share 192.168.10.0/24(rw,sync,no_subtree_check,root_squash) What this does:
-
rw: read/write access -
sync: synchronous writes -
no_subtree_check: skips subtree checking -
root_squash: remote root doesn't get root privileges on the server
Apply and verify:
sudo exportfs -rav sudo exportfs -v sudo ss -lntup | grep ':2049' 11. Open the right ports
| Port | Protocol | Needed for |
|---|---|---|
| 2049 | TCP | NFS (NFSv4) |
| 445 | TCP | SMB/CIFS, only if you also run Samba |
Port 445 is not required for NFS.
Allow NFS from your client subnet:
openstack security group rule create \ --ingress \ --protocol tcp \ --dst-port 2049 \ --remote-ip <CLIENT_CIDR> \ <SECURITY_GROUP_ID> Only if you're also deploying SMB/CIFS:
openstack security group rule create \ --ingress \ --protocol tcp \ --dst-port 445 \ --remote-ip <CLIENT_CIDR> \ <SECURITY_GROUP_ID> Opening 445 alone doesn't give you SMB. You still need Samba (or similar) installed and configured.
Verify:
openstack security group rule list <SECURITY_GROUP_ID> This guide assumes NFSv4 over TCP 2049. If you use NFSv3, additional RPC-related ports and services come into play.
12. Mount the share from a client VM
On an Ubuntu client:
sudo apt update sudo apt install -y nfs-common sudo mkdir -p /mnt/shared-storage sudo mount -t nfs <NFS_SERVER_PRIVATE_IP>:/srv/nfs/share /mnt/shared-storage For example:
sudo mount -t nfs 192.168.10.20:/srv/nfs/share /mnt/shared-storage Verify it works:
df -h /mnt/shared-storage findmnt /mnt/shared-storage touch /mnt/shared-storage/test-file.txt ls -l /mnt/shared-storage If you mount the same export from a second client, you should see the file there too. That's your shared storage working. 🎉
Mount automatically at boot
Add this to /etc/fstab on the client:
<NFS_SERVER_PRIVATE_IP>:/srv/nfs/share /mnt/shared-storage nfs defaults,_netdev,nofail 0 0 And test it:
sudo mount -a findmnt /mnt/shared-storage Troubleshooting
On the server:
sudo systemctl status nfs-kernel-server sudo exportfs -v sudo ss -lntup | grep ':2049' df -h /srv/nfs/share From the client:
ping <NFS_SERVER_PRIVATE_IP> nc -zv <NFS_SERVER_PRIVATE_IP> 2049 showmount -e <NFS_SERVER_PRIVATE_IP> A few notes on those checks:
-
pingmay fail if ICMP is blocked, even when NFS works fine. Thenccheck on port 2049 is the more meaningful one. -
showmountrelies onmountdand may fail on an NFSv4-only setup. A failure there doesn't mean NFSv4 is down.
Common issues
| Symptom | What to check |
|---|---|
| Connection timeout | Neutron routing, Security Group rules, server IP |
| Permission denied | /etc/exports, export options, filesystem ownership, client UID/GID |
| Mount fails after reboot | /etc/fstab, filesystem UUID, whether the Cinder volume is attached |
| Directory appears empty | Make sure the volume is mounted on /srv/nfs before NFS starts exporting |
| Cannot write files | Directory permissions and export configuration |
| Out of space | df -h and the Cinder volume size |
Final thoughts
You now have shared file storage across OpenStack instances without Manila: a Cinder volume, exported by an NFS server, and mounted by your clients over a private network.
Before you take this to production, keep these points in mind:
- A dedicated project is organization, not isolation. It doesn't replace proper network isolation and access control.
- Back up the Cinder volume according to your operational requirements.
- Restrict SSH and NFS to authorized source networks only.
- Keep UID/GID mappings consistent across clients, and set proper filesystem permissions.
- Single point of failure: one NFS server means one thing that can go down. Production setups may need HA, recovery, and monitoring.
- No quotas: the Cinder volume size sets the filesystem capacity. It's not a Manila-style share quota.
Have you built something similar, or found a better way to handle HA for this setup? Let me know in the comments! 👇