Table of Contents

As architects designing robust containerization platforms, strict control over resource management and security context is paramount. Ephemeral storage, typically handled by tmpfs mounts, is a critical component for applications requiring high-speed temporary file access, such as caching, session management, or build artifact generation. A common friction point arises when developers need to execute binaries or scripts from a tmpfs location, which Docker often prevents by default for security reasons.
This article addresses the specific challenge of configuring custom tmpfs mount options, such as enabling execution privileges (exec), when interacting with the Docker Engine using the high-level Docker SDK for Python (docker-py). Understanding the impedance mismatch between the SDK’s abstractions and the underlying Docker Remote API is essential for advanced configuration.
PROBLEM STATEMENT
The core issue stems from the design of the docker.types.Mount helper class within the Docker SDK for Python. While this class simplifies volume management, it does not provide an explicit parameter to pass arbitrary kernel mount options required for tmpfs configuration, such as exec, nosuid, or nodev overrides.
The Mount class definition for tmpfs only exposes the following relevant parameters:
class Mount(target, ..., type='volume', ..., tmpfs_size=None, tmpfs_mode=None)
When a tmpfs mount is created using the standard method, Docker Engine defaults to a secure configuration, typically including noexec, nosuid, and nodev options. If an application attempts to run an executable stored in this mounted path, the container runtime will throw a permissions error, often manifesting as “Permission denied,” even if the file permissions (mode) are correctly set to executable.
For example, using the standard SDK approach:
from docker.types import Mount
# This will result in a noexec mount by default
mount_config = Mount(
target='/mnt/temp_exec',
source='',
type='tmpfs',
tmpfs_size='100m',
tmpfs_mode=0o777
)
# client.containers.run(..., mounts=[mount_config])
# Fails execution attempts within /mnt/temp_exec
Since there is no parameter on the Mount object to specify exec, the default, more restrictive behavior persists.
SOLUTION
The robust solution is to bypass the high-level docker.types.Mount abstraction and leverage the ability of the Docker SDK to accept raw dictionary definitions for container mounts. The client.containers.run and client.containers.create methods accept a list of either Mount objects or dictionaries that conform directly to the Docker Remote API specification for mount objects.
The key insight is utilizing the TmpfsOptions field, which maps directly to the underlying kernel mount options when defining a mount of type tmpfs.
Crafting the Raw Mount Dictionary
To enable execution on the tmpfs mount, you must explicitly pass the exec option within the TmpfsOptions list. Since the Docker Engine defaults typically include nosuid and nodev, you generally only need to specify the options you want to include, plus any required configuration like size and mode.
Note on Default Overriding: When using TmpfsOptions, you are providing the complete set of kernel mount options. Therefore, if you need a specific mode, you must include it here (e.g., mode=1777 or mode=0755). Docker usually defaults the mode to 1777 (sticky bit).
Implementation using Raw Dictionary
import docker
client = docker.from_env()
# Define the custom tmpfs configuration using a raw dictionary
# This structure conforms to the Docker Remote API 'Mount' object
tmpfs_exec_mount = {
'Type': 'tmpfs',
'Target': '/app/exec_cache',
'TmpfsSize': 52428800, # 50 MB in bytes
# TmpfsOptions is the crucial field
'TmpfsOptions': [
'exec', # Explicitly enables execution
'mode=1777', # Standard permissions for /tmp type storage
'size=50m' # Matches TmpfsSize, good practice for clarity
]
}
# Run the container, passing the dictionary directly to the 'mounts' argument
try:
container = client.containers.run(
image='alpine/git',
command=['sh', '-c', 'mount | grep /app/exec_cache'],
detach=False,
remove=True,
mounts=[tmpfs_exec_mount]
)
print("Mount successful. Output from container:")
print(container)
except docker.errors.ContainerError as e:
print(f"Container error: {e}")
# Verification step (checking mount options inside the container)
# The output should show options including 'rw,relatime,exec'
By switching from the SDK helper object to the underlying dictionary structure, senior engineers retain full control over all documented Docker Engine API fields, ensuring granular configuration that high-level abstractions might obscure.
ALTERNATIVE SOLUTIONS
While using the raw dictionary for the mounts parameter is the cleanest SDK-based solution, several alternatives exist, each with different architectural tradeoffs.
1. Using the Lower-Level API Client
If extensive manipulation of the raw API payload is required (e.g., managing complex network configurations simultaneously), bypassing the high-level client.containers.run method entirely and using the low-level API client provides maximum control.
api_client = client.api
host_config = api_client.create_host_config(
Mounts=[tmpfs_exec_mount] # Use the raw dict structure here
)
# Then use api_client.create_container(...) and api_client.start(...)
Tradeoff: Offers absolute control but increases boilerplate code complexity compared to the simplicity of containers.run.
2. Remounting Inside the Container (Requires Elevated Privileges)
A less desirable workaround involves mounting the tmpfs normally (as noexec) and then using shell commands inside the entrypoint to remount the volume with exec enabled. This requires the container to run in an elevated security context (often privileged=True or specific capabilities), which is generally a significant security risk and should be avoided.
Tradeoff: Simple command modification but introduces severe security debt and requires root capabilities inside the container.
3. Defining a Custom Storage Driver or Volume Plugin (High Complexity)
For environments where this behavior must be standardized across many teams, defining a custom volume driver or plugin that defaults tmpfs creation to include exec options can be considered. This moves the configuration burden away from the application code and into the infrastructure layer.
Tradeoff: Architecturally clean for standardization but involves substantial operational overhead and platform complexity.
4. Using a Host Bind Mount
If the need for tmpfs performance is not strictly mandatory, a standard host bind mount can be used. Host bind mounts generally respect the kernel mount options of the host filesystem, which are typically exec by default. You can create a directory on the host and bind it, ensuring the host path allows execution.
Tradeoff: Sacrifices the isolation and automatic cleanup benefits of tmpfs and couples the container to the host filesystem structure.
WRAP UP
The ability to pass specific tmpfs mount options like exec or noexec is critical for securing and optimizing containerized workloads. The limitation within the docker.types.Mount helper class illustrates a classic tradeoff in software architecture: abstraction versus explicit control. For advanced configuration, the recommended approach is to leverage the flexibility of the Docker SDK for Python by passing raw dictionary structures directly to the mounts argument, adhering strictly to the underlying Docker Remote API specification (specifically utilizing the TmpfsOptions key).
This technique ensures that engineers can achieve the required security posture and functional requirements without resorting to insecure workarounds like remounting inside the container or overly permissive host binds. For teams working with complex Docker API integrations or container security architectures, it’s often beneficial to hire Python engineers with deep experience in secure, containerized systems.
Social Hashtags
#Docker #PythonDocker #Tmpfs #ContainerSecurity #DockerSDK #DevOps #CloudNative #PlatformEngineering #Kubernetes #LinuxContainers #InfrastructureAsCode
Frequently Asked Questions
Docker applies secure defaults to minimize the attack surface. noexec prevents unexpected binaries or scripts dropped into temporary directories (like cache or scratch space) from being executed, mitigating risks associated with untrusted inputs or potential container escapes via local execution exploits.
Yes. When providing the raw dictionary input to the Docker SDK, the keys must match the exact case required by the underlying Docker Remote API specification, which is generally PascalCase for configuration keys, such as Type, Target, and TmpfsOptions.
When using the raw dictionary structure, you should include TmpfsSize as a separate key (usually in bytes). However, the kernel-level mode and size can also be defined within the TmpfsOptions list (e.g., mode=1777 and size=50m). It is best practice to include both TmpfsSize and the size option in TmpfsOptions for clarity and maximum compatibility.
No. The TmpfsOptions field is specific to mounts of Type: tmpfs. For bind mounts, kernel options are controlled via the BindOptions key and must adhere to the host kernel's capabilities and security rules.











