diff --git a/.gitignore b/.gitignore
index 94035e0..bdd28de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ version.txt
tox_docker.egg-info
build/
dist/
+.idea
diff --git a/README.rst b/README.rst
index 5bd8bb2..c9d7cdd 100644
--- a/README.rst
+++ b/README.rst
@@ -109,11 +109,12 @@ The ``[docker:container-name]`` section may contain the following directives:
``volumes``
A multi-line list of `volumes
`__ to make available to the
- container, as ``:::``.
- The ``type`` must be ``bind``, and the only supported options are ``rw``
- (read-write) or ``ro`` (read-only). The ``outside_path_or_name`` must
- be a path that exists on the host system. Both the ``outside_path``
- and ``inside_path`` must be absolute paths.
+ container, as ``::[:]``.
+ The ``type`` must be ``bind`` or ``tmpfs``. For ``bind`` type the only supported options are ``rw``
+ (read-write) or ``ro`` (read-only). The ``tmpfs`` type additionally supports ``size`` and ``mode`` options with ``;`` as delimiter.
+ The ``outside_path`` is required for the ``bind`` type and must be a path that exists on the host system.
+ For the ``tmpfs`` type ``outside_path`` should NOT be specified.
+ Both the ``outside_path`` and ``inside_path`` must be absolute paths.
``healthcheck_cmd``, ``healthcheck_interval``, ``healthcheck_retries``, ``healthcheck_start_period``, ``healthcheck_timeout``
These set or customize parameters of the container `health check
@@ -183,12 +184,14 @@ Example
healthcheck_retries = 30
healthcheck_interval = 1
healthcheck_start_period = 1
- # Configure a bind-mounted volume on the host to store Postgres' data
+ # Configure a bind-mounted volume on the host to store Postgres' data and tmpfs as /tmp/
# NOTE: this is included for demonstration purposes of tox-docker's
- # volume capability; you probably _don't_ want to do this for real
+ # volume capability; you probably _don't_ want to use bind mounts for real
# testing use cases, as this could persist data between test runs
volumes =
bind:rw:/my/own/datadir:/var/lib/postgresql/data
+ tmpfs:rw;size=64m;mode=1777:/tmp/
+
[docker:appserv]
# You can use any value that `docker run` would accept as the image
diff --git a/tox.ini b/tox.ini
index cf96c01..299f6ee 100644
--- a/tox.ini
+++ b/tox.ini
@@ -70,6 +70,7 @@ healthcheck_timeout = 1
healthcheck_start_period = 1
volumes =
bind:rw:{toxworkdir}:/healthcheck/web
+ tmpfs:rw;size=1m;mode=1777:/tmp/
# do NOT add this env to the envlist; it is supposed to fail,
# and the CI scripts run it directly with this expectation
diff --git a/tox_docker/config.py b/tox_docker/config.py
index 6ec0118..9e5193a 100644
--- a/tox_docker/config.py
+++ b/tox_docker/config.py
@@ -1,5 +1,5 @@
from pathlib import Path
-from typing import Collection, Dict, List, Mapping, Optional
+from typing import Collection, Dict, List, Mapping, Optional, Union
import os
import os.path
import re
@@ -118,25 +118,69 @@ def __init__(self, config_line: str) -> None:
class Volume:
def __init__(self, config_line: str) -> None:
parts = config_line.split(":")
- if len(parts) != 4:
+ if len(parts) < 3 or len(parts) > 4:
raise ValueError(f"Volume {config_line!r} is malformed")
- if parts[0] != "bind":
- raise ValueError(f"Volume {config_line!r} type must be 'bind:'")
- if parts[1] not in ("ro", "rw"):
- raise ValueError(f"Volume {config_line!r} options must be 'ro' or 'rw'")
-
- volume_type, mode, outside, inside = parts
- if not os.path.isabs(outside):
- raise ValueError(f"Volume source {outside!r} must be an absolute path")
- if not os.path.isabs(inside):
- raise ValueError(f"Mount point {inside!r} must be an absolute path")
-
- self.docker_mount = Mount(
- source=outside,
- target=inside,
- type=volume_type,
- read_only=bool(mode == "ro"),
- )
+
+ volume_type, options_str, *_outside_path, inside_path = parts
+
+ if not os.path.isabs(inside_path):
+ raise ValueError(f"Mount point {inside_path!r} must be an absolute path")
+
+ mount_params = {
+ "target": inside_path,
+ "type": volume_type,
+ **self._parse_options(config_line, volume_type, options_str),
+ }
+
+ # bind-specific checks and setup
+ if volume_type == "bind":
+ if len(_outside_path) != 1:
+ raise ValueError(
+ f"Volume {config_line!r} of type 'bind' must have an outside path"
+ )
+ outside = _outside_path[0]
+
+ if not os.path.isabs(outside):
+ raise ValueError(f"Volume source {outside!r} must be an absolute path")
+ mount_params["source"] = outside
+ # tmpfs-specific setup
+ elif volume_type == "tmpfs":
+ # tmpfs does not have source, so emtpy string
+ mount_params["source"] = ""
+ else:
+ raise ValueError(f"Volume {config_line!r} type must be 'bind' or 'tmpfs'")
+
+ self.docker_mount = Mount(**mount_params)
+
+ def _parse_options(
+ self, config_line: str, volume_type: str, options_str: str
+ ) -> dict:
+ """Parse volume options into `Mount()` params."""
+ result: Dict[str, Union[str, int, bool]] = {}
+ (
+ access_mode,
+ *other_options,
+ ) = options_str.split(";")
+
+ # parsing access mode
+ if access_mode not in ("ro", "rw"):
+ raise ValueError(f"Volume {config_line!r} access mode must be 'ro' or 'rw'")
+ result["read_only"] = bool(access_mode == "ro")
+
+ # parsing tmpfs-specific options
+ if volume_type == "tmpfs":
+ for other_option in other_options:
+ key, value = other_option.split("=")
+ if key == "size": # volume size, such as 64m
+ result["tmpfs_size"] = value
+ elif key == "mode": # permissions, such as 1777
+ result["tmpfs_mode"] = int(value)
+ else:
+ raise ValueError(
+ f"'{other_option!r}' is not a valid option for volume of type '{volume_type}'"
+ )
+
+ return {}
class ContainerConfig:
diff --git a/tox_docker/plugin.py b/tox_docker/plugin.py
index b6e38c4..46632a6 100644
--- a/tox_docker/plugin.py
+++ b/tox_docker/plugin.py
@@ -190,7 +190,7 @@ def docker_run(
for mount in container_config.mounts:
source = mount["Source"]
- if not os.path.exists(source):
+ if mount["Type"] != "tmpfs" and not os.path.exists(source):
raise ValueError(f"Volume source {source!r} does not exist")
assert container_config.runnable_image