Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Dockerfile Breakdown

Relevant source files

Purpose and Scope

This page provides a line-by-line analysis of the Dockerfile that constructs the docker-sshfs container image. The Dockerfile builds a complete SSHFS/Samba bridge server starting from a base Ubuntu image, installing necessary packages, configuring users and permissions, and setting up the critical filesystem integration that enables remote SSH filesystems to be accessed via SMB on macOS.

For runtime directory structure details, see Filesystem Layout. For Samba configuration specifics, see Samba Configuration (smb.conf)). For FUSE settings, see FUSE Configuration. For how the container starts and runs, see Service Lifecycle.

Sources: Dockerfile:1-34


Build Process Overview

The Dockerfile consists of distinct build stages that layer functionality progressively. Each RUN command creates a new image layer that contributes to the final container.

graph TD
    BaseImage["ubuntu:latest\n(Base Layer)"]
PackageLayer["apt-get update && install\nsshfs + samba\n(Package Layer)"]
UserLayer["useradd sshuser\necho sshuser:sshpass\n(User Layer)"]
DirLayer["mkdir /remote\nmkdir /samba-share\n(Directory Layer)"]
ConfigLayer["COPY smb.conf\n(Configuration Layer)"]
PermLayer["chmod 777 /samba-share\n(Permissions Layer)"]
FuseLayer["echo user_allow_other >> fuse.conf\n(FUSE Layer)"]
ExposeLayer["EXPOSE 139 445\n(Port Declaration)"]
LinkLayer["ln -s /remote /samba-share/remote\n(Integration Layer)"]
CmdLayer["CMD smbd --foreground\n(Startup Layer)"]
BaseImage --> PackageLayer
 
   PackageLayer --> UserLayer
 
   UserLayer --> DirLayer
 
   DirLayer --> ConfigLayer
 
   ConfigLayer --> PermLayer
 
   PermLayer --> FuseLayer
 
   FuseLayer --> ExposeLayer
 
   ExposeLayer --> LinkLayer
 
   LinkLayer --> CmdLayer

Dockerfile Build Layers

Each layer builds on the previous, with dependencies flowing from top to bottom. The symbolic link layer is the final filesystem modification before container startup.

Sources: Dockerfile:2-33


Base Image Selection

Dockerfile2 specifies ubuntu:latest as the base image. Ubuntu is chosen because:

  • Provides mature, well-tested FUSE implementation
  • Includes apt-get package manager with comprehensive SSHFS and Samba packages
  • Widely used base with extensive community support
  • Contains necessary Linux kernel features for FUSE mounts

The latest tag automatically pulls the most recent Ubuntu LTS release, ensuring up-to-date packages and security patches.

Sources: Dockerfile2


Package Installation Layer

Dockerfile:5-6 performs a single-layer installation of both core packages:

PackagePurposeProvides
sshfsRemote filesystem clientSSHFS binary, FUSE dependencies, SSH client
sambaSMB serversmbd daemon, SMB protocol implementation
graph LR
    AptGet["apt-get install"]
subgraph "sshfs Package"
        SSHFSBin["sshfs binary"]
FuseDeps["libfuse2/libfuse3"]
SSHClient["openssh-client"]
end
    
    subgraph "samba Package"
        SMBDBin["smbd binary"]
SMBLibs["libsmbclient"]
SMBConf["Default /etc/samba/smb.conf"]
end
    
 
   AptGet --> SSHFSBin
 
   AptGet --> FuseDeps
 
   AptGet --> SSHClient
 
   AptGet --> SMBDBin
 
   AptGet --> SMBLibs
 
   AptGet --> SMBConf
    
 
   SSHFSBin --> FuseDeps
 
   SSHFSBin --> SSHClient
 
   SMBDBin --> SMBLibs

The packages are installed in a single RUN command to minimize layer count. The -y flag auto-accepts prompts, enabling non-interactive installation.

Package Dependencies

The sshfs package automatically pulls FUSE userspace libraries and SSH client components. The samba package installs the smbd daemon along with protocol libraries.

Sources: Dockerfile:5-6


User Account Creation

Dockerfile9 creates a non-root user account with specific credentials:

  • Username: sshuser
  • Password: sshpass
  • Home Directory: /home/sshuser (created via -m flag)

Why Non-Root User

The sshuser account is critical for two reasons:

  1. SSHFS Mount Ownership: SSHFS mounts are owned by the user who initiates the mount. Running SSHFS as sshuser ensures consistent file ownership (UID 1000, GID 1000).

  2. Samba Force User: The smb.conf forces all SMB operations to run as sshuser, ensuring that file permissions match between SSHFS and Samba regardless of how macOS authenticates.

This account is used for interactive operations (via docker exec -it) but not for running the smbd daemon itself, which runs as root but operates on behalf of sshuser due to the force user directive.

Sources: Dockerfile9


Directory Structure Setup

Dockerfile12 and Dockerfile15 create the two critical directories in the container's filesystem:

DirectoryPurposeMount Role
/remoteSSHFS mount pointTarget for sshfs user@host:path /remote
/samba-shareSamba root directoryServed as SMB share; contains symlink to /remote
graph TB
    RemoteDir["/remote\n(SSHFS mount target)"]
SambaDir["/samba-share\n(Samba root)"]
SymLink["/samba-share/remote\n(symbolic link)"]
RemoteDir -.->|linked by| SymLink
 
   SambaDir -->|contains| SymLink
    
    SSHFSMount["sshfs command\n(runtime)"]
SMBDServe["smbd daemon\n(serving)"]
SSHFSMount -->|mounts to| RemoteDir
 
   SMBDServe -->|serves| SambaDir

These directories are initially empty. The /remote directory receives the SSHFS mount at runtime, while /samba-share is immediately accessible to Samba.

Directory Relationship

The symbolic link (created later in the Dockerfile) connects these directories, enabling files mounted at /remote to be accessible via /samba-share/remote.

Sources: Dockerfile12 Dockerfile15


Configuration File Integration

Dockerfile18 copies the custom Samba configuration file from the build context into the container. This replaces the default smb.conf installed by the samba package.

The custom configuration includes:

  • Guest access enabled (map to guest = Bad User)
  • Force user directive (force user = sshuser)
  • Protocol restrictions (minimum SMB2)
  • Share definition for /samba-share

The build fails if smb.conf is not present in the build context. For complete configuration details, see Samba Configuration (smb.conf)).

Sources: Dockerfile18


Permission Configuration

Dockerfile:20-21 sets permissions on the Samba share directory. Note that line 20 is commented out, indicating a design decision:

  • Commented: chown -R sshuser:sshuser /samba-share (ownership change)
  • Active: chmod -R 777 /samba-share (permission change)
graph LR
    FileOps["File Operations"]
subgraph "Without 777"
        SMBAsRoot["smbd (root)"]
SMBForce["force user: sshuser"]
FUSEOwner["FUSE mount owner: sshuser"]
NoAccess["Permission denied"]
end
    
    subgraph "With 777"
        SMBAsRoot2["smbd (root)"]
SMBForce2["force user: sshuser"]
FUSEOwner2["FUSE mount owner: sshuser"]
WriteAccess["Write successful"]
end
    
 
   FileOps --> SMBAsRoot
 
   SMBAsRoot --> SMBForce
 
   SMBForce --> FUSEOwner
 
   FUSEOwner --> NoAccess
    
 
   FileOps --> SMBAsRoot2
 
   SMBAsRoot2 --> SMBForce2
 
   SMBForce2 --> FUSEOwner2
 
   FUSEOwner2 --> WriteAccess

Permission Strategy

The 777 permission (read/write/execute for all users) is chosen over specific ownership for a critical reason:

The 777 permissions ensure that regardless of the complex UID/GID mapping between smbd (running as root but forced to sshuser) and the SSHFS mount, write operations succeed. This is a pragmatic trade-off favoring functionality over strict permissions within the isolated container environment.

Sources: Dockerfile:20-21


FUSE Configuration

Dockerfile24 modifies the FUSE configuration to enable cross-user filesystem access. This appends the user_allow_other directive to /etc/fuse.conf.

Why user_allow_other is Critical

By default, FUSE mounts are only accessible to the user who created the mount. Without user_allow_other:

  1. sshuser runs: sshfs user@host:path /remote
  2. Mount succeeds, files owned by sshuser
  3. smbd (running as root) tries to access /samba-share/remote
  4. Access denied - different user

With user_allow_other enabled, SSHFS mounts can be accessed by any user, including the smbd process. The allow_other option must also be passed to the sshfs command at runtime (see SSHFS Mount Options).

This is the critical enabler for Samba to serve SSHFS-mounted files.

Sources: Dockerfile24


Port Exposure Declaration

Dockerfile27 declares the ports used by the SMB protocol:

PortProtocolPurpose
139NetBIOS Session ServiceLegacy SMB over NetBIOS
445SMB over TCPDirect SMB (modern)

The EXPOSE directive is documentation only - it does not actually publish ports. Actual port forwarding is configured at container runtime via docker run -p 127.0.0.1:139:139 -p 127.0.0.1:445:445. See Running the Container for runtime port mapping.

Sources: Dockerfile27


Dockerfile30 creates the critical integration point between SSHFS and Samba:

  • Source: /remote (SSHFS mount point)
  • Link: /samba-share/remote (symlink visible to Samba)

This symlink is created at build time, before any SSHFS mount exists. When the SSHFS mount occurs at runtime, the symlink automatically provides access to the mounted files through the Samba share.

This is a zero-copy integration - no data is duplicated. Reads and writes pass directly through the symlink to the FUSE mount.

Sources: Dockerfile30


Container Startup Command

Dockerfile33 defines the container's default startup command:

FlagPurpose
--foregroundRun smbd in foreground instead of daemonizing
--no-process-groupPrevent process group creation
--debug-stdoutSend log output to stdout

Why Foreground Mode

Docker containers require a foreground process as PID 1. When the foreground process exits, the container stops. Running smbd --foreground ensures:

  1. Container remains running while smbd is active
  2. Docker can monitor the process health
  3. Logs are visible via docker logs
  4. Graceful shutdown when container stops

The --no-process-group flag prevents smbd from creating additional process groups, simplifying container lifecycle management. The --debug-stdout flag routes log output to stdout, making it accessible via Docker's logging subsystem.

Sources: Dockerfile33


graph TB
    subgraph "Build Context"
        DockerfileSrc["Dockerfile"]
SmbConfSrc["smb.conf"]
end
    
    subgraph "Base Layer"
        UbuntuImage["ubuntu:latest"]
end
    
    subgraph "Package Layer"
        SSHFSPkg["sshfs package"]
SambaPkg["samba package"]
FuseLib["FUSE libs"]
SSHClient["openssh-client"]
SMBDBinary["smbd binary"]
end
    
    subgraph "User Layer"
        SSHUser["sshuser:1000"]
SSHPass["password: sshpass"]
HomeDir["/home/sshuser"]
end
    
    subgraph "Filesystem Layer"
        RemoteDir["/remote"]
SambaShareDir["/samba-share"]
SymbolicLink["/samba-share/remote -> /remote"]
end
    
    subgraph "Configuration Layer"
        SmbConfFile["/etc/samba/smb.conf"]
FuseConfFile["/etc/fuse.conf"]
PermSambaShare["chmod 777 /samba-share"]
end
    
    subgraph "Runtime Layer"
        SMBDProcess["smbd --foreground"]
Port139["Port 139"]
Port445["Port 445"]
end
    
 
   DockerfileSrc --> UbuntuImage
 
   UbuntuImage --> SSHFSPkg
 
   UbuntuImage --> SambaPkg
 
   SSHFSPkg --> FuseLib
 
   SSHFSPkg --> SSHClient
 
   SambaPkg --> SMBDBinary
    
 
   SSHFSPkg --> SSHUser
 
   SambaPkg --> SSHUser
 
   SSHUser --> SSHPass
 
   SSHUser --> HomeDir
    
 
   SSHUser --> RemoteDir
 
   SSHUser --> SambaShareDir
 
   RemoteDir --> SymbolicLink
 
   SambaShareDir --> SymbolicLink
    
 
   SmbConfSrc --> SmbConfFile
 
   SSHUser --> FuseConfFile
 
   SambaShareDir --> PermSambaShare
    
 
   SmbConfFile --> SMBDProcess
 
   PermSambaShare --> SMBDProcess
 
   SMBDBinary --> SMBDProcess
 
   SMBDProcess --> Port139
 
   SMBDProcess --> Port445

Complete Build Dependency Graph

This diagram maps Dockerfile components to their code entities and runtime dependencies:

This graph shows how each Dockerfile instruction contributes to the final container image, from base packages through configuration to the running smbd process.

Sources: Dockerfile:2-33


Build Layer Size Implications

Each RUN command creates a new image layer. The Dockerfile uses several optimization patterns:

  • Combined Package Installation: Dockerfile:5-6 installs both sshfs and samba in a single RUN to minimize layers
  • Separate Directory Creation: Dockerfile12 and Dockerfile15 create directories in separate commands (could be optimized)
  • Single Configuration Writes: Dockerfile24 appends to fuse.conf in one operation

The largest layers are:

  1. Package installation (100-200 MB including dependencies)
  2. Base Ubuntu image (~70 MB)
  3. Configuration and directory operations (<1 MB each)

Sources: Dockerfile:5-6 Dockerfile12 Dockerfile15 Dockerfile24


Summary

The Dockerfile constructs a complete SSHFS/Samba bridge server through 10 distinct build stages:

  1. Ubuntu base image selection
  2. Package installation (SSHFS + Samba)
  3. User account creation (sshuser)
  4. Directory structure (/remote, /samba-share)
  5. Samba configuration deployment
  6. Permission configuration (777 on /samba-share)
  7. FUSE configuration (user_allow_other)
  8. Port exposure declaration (139, 445)
  9. Symbolic link creation (integration point)
  10. Startup command specification (smbd --foreground)

The resulting image contains all necessary components for protocol translation between SSH and SMB, with the symbolic link at /samba-share/remote serving as the critical zero-copy integration mechanism.

Sources: Dockerfile:1-34