Run managed Samba and S3 storage on Ubuntu 26.04 with ZFS
This guide turns one Ubuntu 26.04 LTS server with a ZFS pool into a small LAN-only NAS. Samba, Cockpit, and the 45Drives management plugins run together in an Incus system container. Garage provides an S3-compatible API from a Docker container on the host.
Keeping Samba and its management interface in the same system container gives
Cockpit direct access to the Unix users, groups, Samba password database, and
service it manages. It avoids synchronizing /etc/passwd, smb.conf, or
passdb.tdb between application containers.
Target environment: Ubuntu 26.04 LTS (Resolute), OpenZFS, Incus, Docker Engine with the Compose plugin, Samba from the Ubuntu repositories, Garage 2.3.0, and 45Drives Cockpit plugins. Validate the complete procedure before changing this note to Tested on.
Compatibility note: Incus supports Ubuntu 26.04, but the published 45Drives packages currently document older Ubuntu releases. The generic plugin installation used below is therefore experimental on Ubuntu 26.04. Test upgrades on a disposable container and keep an Incus snapshot.
RAIDZ protects against a disk failure, but it does not protect against accidental deletion, pool failure, theft, or ransomware. Keep a separate backup of important data.
Architecture
| Component | Runtime | Host endpoint | Persistent storage |
|---|---|---|---|
| Cockpit | Incus container | NAS_IP:9090 |
Container root filesystem |
| Samba | Incus container | NAS_IP:445 |
/srv/nas/shares |
| Garage S3 API | Docker | NAS_IP:3900 |
/srv/nas/garage/data |
| Garage metadata | Docker | Not published | /srv/nas/garage/meta |
Incus proxy devices publish Cockpit and SMB on the selected host address. A shifted disk device maps ownership between the unprivileged Incus container and the host ZFS dataset. Garage remains an ordinary Compose service.
The examples assume:
- the existing ZFS pool is named
zroot; - the NAS has a static LAN address represented by
NAS_IP; - the LAN is trusted and none of these services is exposed to the Internet;
- Docker Engine and the Compose plugin are already installed;
- commands outside
incus execrun on the Ubuntu host.
Verify the host
Check the pool, Docker, and the host address before changing the system:
|
|
Set the address once for commands in the current shell:
|
|
192.0.2.10 is a documentation address and will not work on a real LAN.
Replace it with the static address assigned to the NAS.
Create ZFS datasets
Create separate datasets for ordinary shared files, Garage metadata, and Garage object data:
|
|
Do not enable ZFS deduplication for these datasets. Confirm their effective properties and mount points:
|
|
Install and initialize Incus
Ubuntu 26.04 includes a native Incus package. Install it, initialize the local daemon, and give the administrative account access to the Incus socket:
|
|
Open a new login session after changing group membership. Membership in
incus-admin is effectively root access to the host, so grant it only to
trusted administrators.
Verify the daemon and storage pool:
|
|
Create the Samba system container
Launch an unprivileged Ubuntu 26.04 container:
|
|
Create the target directory before attaching the host dataset. The shift=true
option translates UID and GID values without making the container privileged:
|
|
Publish SMB and Cockpit only on the selected host address:
|
|
Inspect the resulting configuration:
|
|
Install Samba and Cockpit
Install the services inside the system container:
|
|
Create a dedicated administrator. Use a unique password; this account controls
Cockpit and can elevate through sudo:
|
|
Confirm that both services are running:
|
|
Install the 45Drives plugins
cockpit-file-sharing manages shares through Samba’s registry configuration
and can set Samba passwords for existing Unix users. Install its generic build
rather than adding a distribution repository intended for an older Ubuntu
release:
|
|
Cockpit’s standard Accounts page can create local users. For a more complete users-and-groups interface, install the optional Cockpit Identities generic release. This plugin is older and is the least certain part of the Ubuntu 26.04 setup:
|
|
Take a recoverable checkpoint after verifying that the plugins load:
|
|
Configure Samba for registry shares
The File Sharing plugin uses Samba’s registry backend. Back up the package configuration and replace it with a small global configuration that includes the registry:
|
|
Open https://NAS_IP:9090, accept the locally generated certificate warning,
and sign in as nasadmin. Then:
- Open Accounts or Identities and create the local users and groups.
- Open File Sharing → Samba → Manage Samba Passwords and set a separate Samba password for each user who needs SMB access.
- Create the
storageshare with path/srv/shares. - Set Guest Ok to off, Read Only to off, and Browsable to on.
- Add
valid users = @nasusersin advanced settings if access is group-based.
A Samba password does not replace the Unix account. Both records are required, and the user must have Unix permission to the shared directory.
Verify the generated registry configuration:
|
|
Configure share permissions
Create the group if it was not created in the UI, add users to it, and make the
shared directory group-writable. Replace <username> with an actual account:
|
|
The setgid bit makes new directories inherit the nasusers group. Check both
the container view and the shifted ownership visible on the host:
|
|
Do not manually change the shifted numeric ownership on the host unless you understand the container’s ID map.
Configure Garage on the host
Create a small Compose project for Garage only:
|
|
Generate garage.toml and keep its secrets out of version control:
|
|
Create .env, replacing the documentation address:
|
|
Create compose.yaml:
|
|
Protect the Garage datasets and start the service:
|
|
Restrict network access
Allow TCP ports 445, 9090, and 3900 only from the trusted LAN in the host
or upstream firewall. Do not expose SMB, Cockpit, or an unencrypted S3 endpoint
to the Internet. Docker-published ports can bypass simple UFW rules, so enforce
the Garage policy in the DOCKER-USER chain or on the upstream router.
Confirm the listeners on the host:
|
|
Use a VPN for remote administration. If Cockpit must cross an untrusted network, place it behind a correctly configured TLS reverse proxy.
Verify Samba
From another Ubuntu machine, list the shares and transfer a test file:
|
|
On Windows, open \\NAS_IP\storage. Windows may reuse an existing SMB
session; remove a stale one before testing different credentials:
|
|
Verify S3
Load the Garage credentials into a client environment without copying the
whole .env file to an untrusted machine:
|
|
Garage does not implement every AWS S3 feature. Check its S3 compatibility matrix before using applications that depend on uncommon API operations.
Snapshots and backup
Snapshot the Samba system container before plugin or operating-system upgrades:
|
|
The container snapshot protects its users, Samba password database, registry configuration, and Cockpit installation. The attached host dataset is not part of that snapshot. Snapshot the ZFS data separately:
|
|
For the most conservative Garage backup, briefly stop writes and snapshot its metadata and data together:
|
|
Snapshots remain in the same pool. Replicate them or make an independent backup. Never restore only Garage metadata or only Garage data.
Updates
Update Samba and Cockpit inside the system container, then verify both services:
|
|
Review 45Drives release notes before updating either plugin. Because the plugins are installed outside APT, ordinary package upgrades do not update them automatically.
Garage upgrades can require version-specific steps. Read the upstream upgrade instructions before changing the pinned image tag.
Troubleshooting
If Cockpit opens but users or shares cannot be managed, inspect the services, plugin files, Samba registry, and logs inside the container:
|
|
If a user can authenticate but cannot write, check group membership and the container-side permissions:
|
|
If the mounted dataset is missing or shows unexpected ownership, inspect the Incus disk device and ID mapping rather than changing ownership blindly:
|
|
For Garage failures, inspect its status and logs on the host:
|
|
An HTTP 403 response from unauthenticated curl is sufficient to confirm
that the S3 endpoint is reachable. Finally, verify the ZFS pool itself:
|
|