Repository: pantsbuild/pants

Issue:
Add support for adding arbitrary docker flags
**Is your feature request related to a problem? Please describe.**
Docker has a lot of different flags, most of which are not officially supported via pants, I'd like to be able to use them without needing code changes to pants.

E.g. right now I need to add --debug to my command to see some stack traces from docker. Previously I wanted to use the cache_to/from args before they were officially in pants.

I would also like to be able to setup run args, e.g. ports to forward, volumes to map, etc.

**Describe the solution you'd like**
I'd like docker_image and pants.toml to get some extra parameters for extra_command_line_build_args to be passed to docker build and extra_command_line_run_args to be passed to docker run.

Adding formatting for putting variables (eg homedir, pants hash, etc) into args would also be helpful.

**Describe alternatives you've considered**
Adding every flag to pants that does not need special handling seems wasteful.


Discussion:
@kaos thoughts?
yes, this has been agreed before to be a good idea. the main issue has been to come up with the design for it that is intuitive to use. the unfortunate bit is that we have `[docker].build_args` which are for `docker build --arg` so not arbitrary args for the build step, while for `docker run` this already [exists](https://www.pantsbuild.org/2.18/reference/subsystems/docker#run_args).
at this point, probably just adding it with proper docs to get it done could be acceptable. perhaps something along the lines of `[docker].build_cli_args`..

> [...] and extra_command_line_run_args to be passed to docker run.

This is `[docker].run_args`.

I personally need run_args to be in the docker_image target, which I at
least don't see in the docs.
> I personally need run_args to be in the docker_image target, which I at least don't see in the docs.

Ah, right. No, it's only globally in configuration thus far. PR welcome to address these features if you're able to :)
Thoughts on whether the per-target option should shadow or add to the global option?

Resolving patch:
diff --git a/src/python/pants/backend/docker/goals/run_image.py b/src/python/pants/backend/docker/goals/run_image.py
--- a/src/python/pants/backend/docker/goals/run_image.py
+++ b/src/python/pants/backend/docker/goals/run_image.py
@@ -8,7 +8,11 @@
 
 from pants.backend.docker.goals.package_image import BuiltDockerImage, DockerPackageFieldSet
 from pants.backend.docker.subsystems.docker_options import DockerOptions
-from pants.backend.docker.target_types import DockerImageRegistriesField, DockerImageSourceField
+from pants.backend.docker.target_types import (
+    DockerImageRegistriesField,
+    DockerImageRunExtraArgsField,
+    DockerImageSourceField,
+)
 from pants.backend.docker.util_rules.docker_binary import DockerBinary
 from pants.core.goals.package import BuiltPackage, PackageFieldSet
 from pants.core.goals.run import RunFieldSet, RunInSandboxBehavior, RunRequest
@@ -22,6 +26,11 @@ class DockerRunFieldSet(RunFieldSet):
     required_fields = (DockerImageSourceField,)
     run_in_sandbox_behavior = RunInSandboxBehavior.RUN_REQUEST_HERMETIC
 
+    extra_run_args: DockerImageRunExtraArgsField
+
+    def get_run_args(self, options: DockerOptions) -> tuple[str, ...]:
+        return tuple(options.run_args + (self.extra_run_args.value or ()))
+
 
 @rule
 async def docker_image_run_request(
@@ -49,8 +58,13 @@ async def docker_image_run_request(
         Get(EnvironmentVars, EnvironmentVarsRequest(options_env_aware.env_vars)),
         Get(BuiltPackage, PackageFieldSet, build_request),
     )
+
     tag = cast(BuiltDockerImage, image.artifacts[0]).tags[0]
-    run = docker.run_image(tag, docker_run_args=options.run_args, env=env)
+    run = docker.run_image(
+        tag,
+        docker_run_args=field_set.get_run_args(options),
+        env=env,
+    )
 
     return RunRequest(
         digest=image.digest,
diff --git a/src/python/pants/backend/docker/target_types.py b/src/python/pants/backend/docker/target_types.py
--- a/src/python/pants/backend/docker/target_types.py
+++ b/src/python/pants/backend/docker/target_types.py
@@ -12,6 +12,7 @@
 from typing_extensions import final
 
 from pants.backend.docker.registries import ALL_DEFAULT_REGISTRIES
+from pants.backend.docker.subsystems.docker_options import DockerOptions
 from pants.base.build_environment import get_buildroot
 from pants.core.goals.package import OutputPathField
 from pants.core.goals.run import RestartableField
@@ -559,6 +560,14 @@ class DockerImageBuildPlatformOptionField(
     docker_build_option = "--platform"
 
 
+class DockerImageRunExtraArgsField(StringSequenceField):
+    alias: ClassVar[str] = "extra_run_args"
+    default = ()
+    help = help_text(
+        lambda: f"Extra arguments to pass into the invocation of `docker run`. These are in addition to those at the `[{DockerOptions.options_scope}].run_args`"
+    )
+
+
 class DockerImageTarget(Target):
     alias = "docker_image"
     core_fields = (
@@ -584,6 +593,7 @@ class DockerImageTarget(Target):
         DockerImageBuildImageCacheToField,
         DockerImageBuildImageCacheFromField,
         DockerImageBuildImageOutputField,
+        DockerImageRunExtraArgsField,
         OutputPathField,
         RestartableField,
     )


Test patch:
diff --git a/src/python/pants/backend/docker/goals/run_image_integration_test.py b/src/python/pants/backend/docker/goals/run_image_integration_test.py
--- a/src/python/pants/backend/docker/goals/run_image_integration_test.py
+++ b/src/python/pants/backend/docker/goals/run_image_integration_test.py
@@ -5,6 +5,9 @@
 
 from textwrap import dedent
 
+import pytest
+
+from pants.backend.docker.target_types import DockerImageRunExtraArgsField
 from pants.testutil.pants_integration_test import PantsResult, run_pants, setup_tmpdir
 
 
@@ -20,22 +23,37 @@ def run_pants_with_sources(sources: dict[str, str], *args: str) -> PantsResult:
         )
 
 
-def test_docker_run() -> None:
+@pytest.mark.parametrize("override_run_args", [True, False])
+def test_docker_run(override_run_args) -> None:
+    """This test exercises overriding the run args at the target level.
+
+    It works by setting a default value for an envvar in the Dockerfile with the `ENV` statement,
+    and then overriding that envvar with the `-e` option on the target level.
+    """
+
+    expected_value = "override" if override_run_args else "build"
+    docker_run_args_attribute = (
+        f'{DockerImageRunExtraArgsField.alias}=["-e", "mykey=override"]'
+        if override_run_args
+        else ""
+    )
+
     sources = {
-        "BUILD": "docker_image(name='run-image')",
+        "BUILD": f"docker_image(name='run-image', {docker_run_args_attribute})",
         "Dockerfile": dedent(
             """\
             FROM alpine
-            CMD echo "Hello from Docker image"
+            ENV mykey=build
+            CMD echo "Hello from Docker image with mykey=$mykey"
             """
         ),
     }
+
     result = run_pants_with_sources(
         sources,
         "run",
         "{tmpdir}:run-image",
     )
-
     print("pants stderr\n", result.stderr)
-    assert "Hello from Docker image\n" == result.stdout
+    assert f"Hello from Docker image with mykey={expected_value}\n" == result.stdout
     result.assert_success()