2026-02-18 18:19:42 +01:00
2026-02-18 18:19:42 +01:00
2026-02-18 18:19:42 +01:00
2026-02-18 18:06:44 +01:00
2026-02-18 12:09:38 +01:00
2026-02-18 18:06:44 +01:00
2026-02-18 18:06:44 +01:00
2026-02-18 18:15:00 +01:00
2026-02-18 17:39:04 +01:00

AD-Integrated Containerized Samba File Server

This repository provides a production-oriented Samba file server container that joins an existing Active Directory domain, exposes static and dynamic SMB shares, and persists share identity by AD objectGUID.

Architecture

  • Samba runs in ADS mode with winbind identity mapping.
  • Static shares:
    • \\server\Private -> /data/private
    • \\server\Public -> /data/public
  • Dynamic shares are generated from AD groups matching FileShare_* and written to /etc/samba/generated/shares.conf.
  • Dynamic share records are persisted in SQLite at /state/shares.db.
  • Backing storage is GUID-based and stable across group rename:
    • /data/groups/<objectGUID>
  • Samba machine trust/key material is persisted in /var/lib/samba to survive container recreation.
  • Container hostname is fixed (SAMBA_HOSTNAME) to keep AD computer identity stable.
  • NetBIOS name defaults to ADSAMBAFSRV and is clamped to 15 characters (NETBIOS_NAME override supported).
  • Setup prompts for well-known authorization groups by SID (DOMAIN_USERS_SID, DOMAIN_ADMINS_SID) to avoid localized group names.
  • Startup resolves those SIDs to NSS group names via winbind, then uses those resolved groups in Samba valid users rules.
  • Share operations are audited with Samba full_audit (connect, list, read, write, create, delete, rename) and written to Samba log files.
  • Reconciliation is executed:
    • once on startup
    • every 5 minutes via cron

Dynamic Share Lifecycle (objectGUID-first)

The reconciliation script (/app/reconcile_shares.py) enforces these rules:

  1. New matching group -> insert DB row, create /data/groups/<objectGUID>, expose share.
  2. Group renamed but still FileShare_ -> update samAccountName and shareName, keep same path.
  3. Group removed or no longer matches prefix -> set isActive=0, remove Samba exposure, keep data.
  4. Previously inactive group returns -> set isActive=1, reuse existing path and data.

SQLite State Database

Database path: /state/shares.db

Table schema:

CREATE TABLE shares (
  objectGUID TEXT PRIMARY KEY,
  samAccountName TEXT NOT NULL,
  shareName TEXT NOT NULL,
  path TEXT NOT NULL,
  createdAt TIMESTAMP NOT NULL,
  lastSeenAt TIMESTAMP NOT NULL,
  isActive INTEGER NOT NULL
);

AD Requirements

  • Existing AD DS domain reachable from the Docker host.
  • Initial admin credentials with rights to create/reset FileShare_ServiceAccount during ./setup.
  • FileShare_ServiceAccount must be allowed to join computers to the domain (net ads join) in your AD policy.
  • Dynamic group discovery primarily uses machine-account LDAP (net ads search -P); join credentials are only used as a fallback LDAP bind path.
  • Group naming convention for dynamic shares:
    • FileShare_<ShareName>

DNS Requirements

  • Container must resolve AD DNS records (especially SRV records for domain controllers).
  • DOMAIN should resolve from inside the container.
  • Preferred setup: Docker host uses AD-integrated DNS or forwards to AD DNS.

Time Sync Requirements

Kerberos requires close time alignment.

  • Docker host clock must be synchronized (NTP/chrony/systemd-timesyncd).
  • AD domain controllers must also be time-synchronized.
  • If join/authentication fails unexpectedly, check time skew first.

Repository Layout

  • Dockerfile
  • docker-compose.yml
  • setup
  • .env.example
  • README.md
  • app/init.sh
  • app/reconcile_shares.py
  • etc/samba/smb.conf
  • etc/samba/generated/

Setup

  1. Run interactive setup:

    ./setup
    
  2. If .env is missing, you will be prompted for:

    • REALM
    • WORKGROUP
    • DOMAIN
    • initial admin credentials (used once for provisioning)
    • DOMAIN_USERS_SID
    • DOMAIN_ADMINS_SID
    • optional PUBLIC_GROUP_SID (defaults to DOMAIN_USERS_SID)

    Optional:

    • SAMBA_HOSTNAME (defaults to adsambafsrv)
    • NETBIOS_NAME (defaults to ADSAMBAFSRV, max 15 chars)
  3. Setup behavior:

    • creates or updates AD service account from desired name FileShare_ServiceAccount
    • uses a valid AD sAMAccountName (max 20 chars); default effective value is FileShare_ServiceAcc
    • always sets a long random password
    • writes only service-account credentials to .env (initial admin credentials are not stored)
  4. The setup script then starts the service with:

    docker compose up -d
    
  5. After startup:

    • container joins AD (idempotent)
    • startup reconciliation runs
    • cron runs reconciliation every 5 minutes

SMB Shares

Private

  • Share: \\server\Private
  • Root path: /data/private
  • Per-user path: /data/private/<samAccountName>
  • Script ensures user directories exist and assigns ownership through winbind identity resolution.
  • Permissions:
    • owner user: full control
    • Domain Admins: ACL full control
    • mode: 700
  • hide unreadable = yes + ACLs enforce that users only see their own folder.

Public

  • Share: \\server\Public
  • Path: /data/public
  • Read/write for authenticated users in configurable PUBLIC_GROUP_SID (default: DOMAIN_USERS_SID, resolved through winbind).
  • No guest access.

Dynamic Group Shares

  • AD groups: FileShare_*
  • Share name: prefix removed (FileShare_Finance -> \\server\Finance)
  • Backing path: /data/groups/<objectGUID>
  • Share exposure generated in /etc/samba/generated/shares.conf
  • Dynamic share names are validated for SMB compatibility and deduplicated case-insensitively.

Useful Commands

docker compose logs -f samba
docker compose exec samba python3 /app/reconcile_shares.py
docker compose exec samba sqlite3 /state/shares.db 'SELECT * FROM shares;'
docker compose exec samba testparm -s
docker compose exec samba sh -lc 'tail -n 200 /var/log/samba/log.*'

Troubleshooting

Domain join fails

  • Verify service account credentials in .env.

  • Verify DNS resolution from container:

    docker compose exec samba getent hosts "$DOMAIN"
    
  • Verify time sync on host and AD DCs.

  • Verify NetBIOS name length is <= 15:

    docker compose exec samba testparm -s | grep -i 'netbios name'
    

Winbind user/group resolution fails

  • Check trust:

    docker compose exec samba wbinfo -t
    
  • List users/groups:

    docker compose exec samba wbinfo -u
    docker compose exec samba wbinfo -g
    

Dynamic shares not appearing

  • Confirm AD groups match FileShare_*.

  • Run manual reconciliation and inspect logs:

    docker compose exec samba python3 /app/reconcile_shares.py
    docker compose exec samba tail -n 100 /var/log/reconcile.log
    
  • Validate generated config:

    docker compose exec samba cat /etc/samba/generated/shares.conf
    

acl_xattr.so or full_audit.so module load error

  • If logs show Error loading module .../vfs/acl_xattr.so (or full_audit.so), your running image is missing Samba VFS modules.

  • Rebuild and restart with the updated image:

    docker compose down
    docker compose up -d --build
    
  • Verify modules exist:

    docker compose exec samba sh -lc 'mods="$(smbd -b | sed -n "s/^ *MODULESDIR: //p" | head -n1)/vfs"; ls -1 "$mods"/acl_xattr.so "$mods"/full_audit.so'
    

Permissions in Private share are incorrect

  • Re-run reconciliation to rebuild private directories and ACLs:

    docker compose exec samba python3 /app/reconcile_shares.py
    
  • Check identity resolution for a user:

    docker compose exec samba id 'EXAMPLE\\alice'
    

Notes

  • User data is never automatically deleted.
  • Inactive/deleted groups only stop being exposed as shares.
  • Data and state survive container restarts via named Docker volumes (/data/*, /state, /var/lib/samba).
Description
No description provided
Readme 193 KiB
Languages
Python 62%
Shell 36%
Dockerfile 2%