• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 23505168284

24 Mar 2026 06:11PM UTC coverage: 92.618% (-0.3%) from 92.918%
23505168284

Pull #23133

github

web-flow
Merge e8047d851 into b84b29b9b
Pull Request #23133: Add buildctl engine

215 of 296 new or added lines in 13 files covered. (72.64%)

233 existing lines in 12 files now uncovered.

91363 of 98645 relevant lines covered (92.62%)

4.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.23
/src/python/pants/backend/docker/target_types.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
10✔
5

6
import os
10✔
7
import re
10✔
8
from abc import ABC, abstractmethod
10✔
9
from collections.abc import Callable, Iterator
10✔
10
from dataclasses import dataclass
10✔
11
from typing import TYPE_CHECKING, ClassVar, cast, final
10✔
12

13
from pants.backend.docker.registries import ALL_DEFAULT_REGISTRIES
10✔
14
from pants.backend.docker.subsystems.docker_options import DockerOptions
10✔
15
from pants.base.build_environment import get_buildroot
10✔
16
from pants.core.goals.package import OutputPathField
10✔
17
from pants.core.goals.run import RestartableField
10✔
18
from pants.engine.addresses import Address
10✔
19
from pants.engine.collection import Collection
10✔
20
from pants.engine.environment import EnvironmentName
10✔
21
from pants.engine.fs import GlobMatchErrorBehavior
10✔
22
from pants.engine.rules import collect_rules, rule
10✔
23
from pants.engine.target import (
10✔
24
    COMMON_TARGET_FIELDS,
25
    AllTargets,
26
    AsyncFieldMixin,
27
    BoolField,
28
    Dependencies,
29
    DictStringToStringField,
30
    Field,
31
    InvalidFieldException,
32
    ListOfDictStringToStringField,
33
    OptionalSingleSourceField,
34
    StringField,
35
    StringSequenceField,
36
    Target,
37
    Targets,
38
)
39
from pants.engine.unions import union
10✔
40
from pants.util.docutil import bin_name, doc_url
10✔
41
from pants.util.frozendict import FrozenDict
10✔
42
from pants.util.meta import classproperty
10✔
43
from pants.util.strutil import help_text, softwrap
10✔
44

45
if TYPE_CHECKING:
46
    from pants.backend.docker.util_rules.docker_build_context import DockerBuildContext
47

48
# Common help text to be applied to each field that supports value interpolation.
49
_interpolation_help = (
10✔
50
    "{kind} may use placeholders in curly braces to be interpolated. The placeholders are derived "
51
    "from various sources, such as the Dockerfile instructions and build args."
52
)
53

54

55
class DockerImageBuildArgsField(StringSequenceField):
10✔
56
    alias = "extra_build_args"
10✔
57
    default = ()
10✔
58
    help = help_text(
10✔
59
        """
60
        Build arguments (`--build-arg`) to use when building this image.
61
        Entries are either strings in the form `ARG_NAME=value` to set an explicit value;
62
        or just `ARG_NAME` to copy the value from Pants's own environment.
63

64
        Use `[docker].build_args` to set default build args for all images.
65
        """
66
    )
67

68

69
class DockerImageContextRootField(StringField):
10✔
70
    alias = "context_root"
10✔
71
    help = help_text(
10✔
72
        """
73
        Specify which directory to use as the Docker build context root. This affects the file
74
        paths to use for the `COPY` and `ADD` instructions. For example, whether
75
        `COPY files/f.txt` should look for the file relative to the build root:
76
        `<build root>/files/f.txt` vs relative to the BUILD file:
77
        `<build root>/path_to_build_file/files/f.txt`.
78

79
        Specify the `context_root` path as `files` for relative to build root, or as `./files`
80
        for relative to the BUILD file.
81

82
        If `context_root` is not specified, it defaults to `[docker].default_context_root`.
83
        """
84
    )
85

86
    @classmethod
10✔
87
    def compute_value(cls, raw_value: str | None, address: Address) -> str | None:
10✔
88
        value_or_default = super().compute_value(raw_value, address=address)
9✔
89
        if isinstance(value_or_default, str) and value_or_default.startswith("/"):
9✔
90
            val = value_or_default.strip("/")
1✔
91
            raise InvalidFieldException(
1✔
92
                softwrap(
93
                    f"""
94
                    The `{cls.alias}` field in target {address} must be a relative path, but was
95
                    {value_or_default!r}. Use {val!r} for a path relative to the build root, or
96
                    {"./" + val!r} for a path relative to the BUILD file
97
                    (i.e. {os.path.join(address.spec_path, val)!r}).
98
                    """
99
                )
100
            )
101
        return value_or_default
9✔
102

103

104
class DockerImageSourceField(OptionalSingleSourceField):
10✔
105
    none_is_valid_value = True
10✔
106
    default = "Dockerfile"
10✔
107

108
    # When the default glob value is in effect, we don't want the normal glob match error behavior
109
    # to kick in for a missing Dockerfile, in case there are `instructions` provided, in which case
110
    # we generate the Dockerfile instead. If there are no `instructions`, or there are both
111
    # `instructions` and a Dockerfile hydrated from the `source` glob, we error out with a message
112
    # to the user.
113
    default_glob_match_error_behavior = GlobMatchErrorBehavior.ignore
10✔
114

115
    help = help_text(
10✔
116
        """
117
        The Dockerfile to use when building the Docker image.
118

119
        Use the `instructions` field instead if you prefer not having the Dockerfile in your
120
        source tree.
121
        """
122
    )
123

124

125
class DockerImageInstructionsField(StringSequenceField):
10✔
126
    alias = "instructions"
10✔
127
    required = False
10✔
128
    help = help_text(
10✔
129
        """
130
        The `Dockerfile` content, typically one instruction per list item.
131

132
        Use the `source` field instead if you prefer having the Dockerfile in your source tree.
133

134
        Example:
135

136
            # example/BUILD
137
            docker_image(
138
              instructions=[
139
                "FROM base/image:1.0",
140
                "RUN echo example",
141
              ],
142
            )
143
        """
144
    )
145

146

147
class DockerImageTagsField(StringSequenceField):
10✔
148
    alias = "image_tags"
10✔
149
    default = ("latest",)
10✔
150
    help = help_text(
10✔
151
        f"""
152

153
        Any tags to apply to the Docker image name (the version is usually applied as a tag).
154

155
        {_interpolation_help.format(kind="tag")}
156

157
        See {doc_url("docs/docker/tagging-docker-images")}.
158
        """
159
    )
160

161

162
class DockerImageDependenciesField(Dependencies):
10✔
163
    supports_transitive_excludes = True
10✔
164

165

166
class DockerImageRegistriesField(StringSequenceField):
10✔
167
    alias = "registries"
10✔
168
    default = (ALL_DEFAULT_REGISTRIES,)
10✔
169
    help = help_text(
10✔
170
        """
171
        List of addresses or configured aliases to any Docker registries to use for the
172
        built image.
173

174
        The address is a domain name with optional port for your registry, and any registry
175
        aliases are prefixed with `@` for addresses in the `[docker].registries` configuration
176
        section.
177

178
        By default, all configured registries with `default = true` are used.
179

180
        Example:
181

182
            # pants.toml
183
            [docker.registries.my-registry-alias]
184
            address = "myregistrydomain:port"
185
            default = false # optional
186

187
            # example/BUILD
188
            docker_image(
189
                registries = [
190
                    "@my-registry-alias",
191
                    "myregistrydomain:port",
192
                ],
193
            )
194

195
        The above example shows two valid `registry` options: using an alias to a configured
196
        registry and the address to a registry verbatim in the BUILD file.
197
        """
198
    )
199

200

201
class DockerImageRepositoryField(StringField):
10✔
202
    alias = "repository"
10✔
203
    help = help_text(
10✔
204
        f"""
205
        The repository name for the Docker image. e.g. `"<repository>/<name>"`.
206

207
        It uses the `[docker].default_repository` by default.
208

209
        {_interpolation_help.format(kind="Repository")}
210

211
        Additional placeholders for the repository field are: `name`, `directory`,
212
        `parent_directory`, and `default_repository`.
213

214
        Registries may also configure the repository value for specific registries.
215

216
        See the documentation for `[docker].default_repository` for more information.
217
        """
218
    )
219

220

221
class DockerImageSkipPushField(BoolField):
10✔
222
    alias = "skip_push"
10✔
223
    default = False
10✔
224
    help = f"If true, do not push this image to registries when running `{bin_name()} publish`."
10✔
225

226

227
OptionValueFormatter = Callable[[str], str]
10✔
228

229

230
class ValidateOptionsMixin(ABC):
10✔
231
    def validate_options(self, options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
232
        """Hook method telling Pants to ignore this option in certain contexts.
233

234
        Can also be used to throw errors simply by raising an exception.
235
        """
236
        return True
3✔
237

238

239
class DockerBuildOptionsFieldMixin(ValidateOptionsMixin, ABC):
10✔
240
    docker_build_option: ClassVar[str]
10✔
241

242
    @abstractmethod
10✔
243
    def docker_build_options(
10✔
244
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
245
    ) -> Iterator[str]:
246
        """Subclasses must implement this, to turn their `self.value` into none, one or more option
247
        values."""
248

249

250
class DockerBuildOptionMultiValueFieldMixin(DockerBuildOptionsFieldMixin, ABC):
10✔
251
    """Inherit this mixin class to provide options to `docker build`."""
252

253
    @abstractmethod
10✔
254
    def docker_build_option_values(
10✔
255
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
256
    ) -> Iterator[str]:
257
        """Subclasses must implement this, to turn their `self.value` into none, one or more option
258
        values."""
259

260
    @final
10✔
261
    def docker_build_options(
10✔
262
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
263
    ) -> Iterator[str]:
264
        for value in self.docker_build_option_values(
3✔
265
            docker=docker, value_formatter=value_formatter
266
        ):
UNCOV
267
            yield from (self.docker_build_option, value)
×
268

269

270
class DockerBuildOptionFieldValueMixin(Field, DockerBuildOptionsFieldMixin, ABC):
10✔
271
    """Inherit this mixin class to provide unary options (i.e. option in the form of `--flag=value`)
272
    to `docker build`."""
273

274
    @final
10✔
275
    def docker_build_options(
10✔
276
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
277
    ) -> Iterator[str]:
278
        if self.value is not None:
3✔
NEW
279
            yield f"{self.docker_build_option}={self.value}"
×
280

281

282
class DockerBuildOptionFieldMultiValueMixin(StringSequenceField, DockerBuildOptionsFieldMixin, ABC):
10✔
283
    """Inherit this mixin class to provide options in the form of `--flag=value1,value2` to `docker
284
    build`."""
285

286
    @final
10✔
287
    def docker_build_options(
10✔
288
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
289
    ) -> Iterator[str]:
290
        if self.value:
3✔
NEW
291
            yield f"{self.docker_build_option}={','.join(list(self.value))}"
×
292

293

294
class DockerBuildOptionFieldMultiValueDictMixin(
10✔
295
    DictStringToStringField, DockerBuildOptionsFieldMixin, ABC
296
):
297
    """Inherit this mixin class to provide options in the form of `--flag=key1=value1,key2=value2`
298
    to `docker build`."""
299

300
    @final
10✔
301
    def docker_build_options(
10✔
302
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
303
    ) -> Iterator[str]:
304
        if self.value:
3✔
UNCOV
305
            yield f"{self.docker_build_option}=" + ",".join(
×
306
                f"{key}={value_formatter(value)}" for key, value in self.value.items()
307
            )
308

309

310
class DockerBuildOptionFieldListOfMultiValueDictMixin(
10✔
311
    ListOfDictStringToStringField, DockerBuildOptionsFieldMixin, ABC
312
):
313
    """Inherit this mixin class to provide multiple key-value options to docker build:
314

315
    `--flag=key1=value1,key2=value2 --flag=key3=value3,key4=value4`
316
    """
317

318
    @final
10✔
319
    def docker_build_options(
10✔
320
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
321
    ) -> Iterator[str]:
322
        if self.value:
3✔
UNCOV
323
            for item in self.value:
×
UNCOV
324
                yield f"{self.docker_build_option}=" + ",".join(
×
325
                    f"{key}={value_formatter(value)}" for key, value in item.items()
326
                )
327

328

329
class DockerBuildOptionFlagFieldMixin(BoolField, DockerBuildOptionsFieldMixin, ABC):
10✔
330
    """Inherit this mixin class to provide optional flags (i.e. add `--flag` only when the value is
331
    `True`) to `docker build`."""
332

333
    @final
10✔
334
    def docker_build_options(
10✔
335
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
336
    ) -> Iterator[str]:
NEW
337
        if self.value:
×
NEW
338
            yield f"{self.docker_build_option}"
×
339

340

341
class BuildctlOptionsFieldMixin(ValidateOptionsMixin, ABC):
10✔
342
    buildctl_option: ClassVar[str]
10✔
343

344
    @abstractmethod
10✔
345
    def buildctl_options(
10✔
346
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
347
    ) -> Iterator[str]:
348
        """Subclasses must implement this, to turn their `self.value` into none, one or more option
349
        values."""
350

351

352
class DockerBuildkitPassthroughFieldMixin(
10✔
353
    BuildctlOptionsFieldMixin, DockerBuildOptionsFieldMixin, ABC
354
):
355
    @classproperty
10✔
356
    def docker_build_option(cls) -> str:
10✔
NEW
357
        return cls.buildctl_option
×
358

359
    def docker_build_options(
10✔
360
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
361
    ) -> Iterator[str]:
362
        return super().buildctl_options(docker=docker, value_formatter=value_formatter)  # type: ignore[safe-super]
3✔
363

364

365
class BuildctlOptionMultiValueFieldMixin(BuildctlOptionsFieldMixin, ABC):
10✔
366
    """Inherit this mixin class to provide multi-value options to `buildctl build`.
367

368
    Yields multiple `--opt value1 --opt value2` pairs.
369
    """
370

371
    @abstractmethod
10✔
372
    def buildctl_option_values(
10✔
373
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
374
    ) -> Iterator[str]:
375
        """Subclasses must implement this, to turn their `self.value` into none, one or more option
376
        values."""
377

378
    @final
10✔
379
    def buildctl_options(
10✔
380
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
381
    ) -> Iterator[str]:
NEW
382
        for value in self.buildctl_option_values(docker=docker, value_formatter=value_formatter):
×
NEW
383
            yield from (self.buildctl_option, value)
×
384

385

386
class BuildctlOptionFieldMultiValueDictMixin(
10✔
387
    DictStringToStringField, BuildctlOptionsFieldMixin, ABC
388
):
389
    """Inherit this mixin class to provide options in the form of `--flag=key1=value1,key2=value2`
390
    to `buildctl build`."""
391

392
    @final
10✔
393
    def buildctl_options(
10✔
394
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
395
    ) -> Iterator[str]:
NEW
396
        if self.value:
×
NEW
397
            yield f"{self.buildctl_option}=" + ",".join(
×
398
                f"{key}={value_formatter(value)}" for key, value in self.value.items()
399
            )
400

401

402
class BuildctlOptionFieldListOfMultiValueDictMixin(
10✔
403
    ListOfDictStringToStringField, BuildctlOptionsFieldMixin, ABC
404
):
405
    """Inherit this mixin class to provide multiple key-value options to buildctl build:
406

407
    `--flag=key1=value1,key2=value2 --flag=key3=value3,key4=value4`
408
    """
409

410
    @final
10✔
411
    def buildctl_options(
10✔
412
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
413
    ) -> Iterator[str]:
NEW
414
        if self.value:
×
NEW
415
            for item in self.value:
×
NEW
416
                yield f"{self.buildctl_option}=" + ",".join(
×
417
                    f"{key}={value_formatter(value)}" for key, value in item.items()
418
                )
419

420

421
class BuildctlOptionFieldValueMixin(Field, BuildctlOptionsFieldMixin, ABC):
10✔
422
    """Inherit this mixin class to provide unary options (i.e. option in the form of `--flag=value`)
423
    to `buildctl build`."""
424

425
    @final
10✔
426
    def buildctl_options(
10✔
427
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
428
    ) -> Iterator[str]:
NEW
429
        if self.value is not None:
×
NEW
430
            yield f"{self.buildctl_option}={self.value}"
×
431

432

433
class BuildctlLayeredOptionFieldValueMixin(Field, BuildctlOptionsFieldMixin, ABC):
10✔
434
    """Inherit this mixin class to provide layered options (i.e. option in the form of `--flag
435
    suboption=value`) to `buildctl build`.
436

437
    You can override the option-value delimiter (default is `=`) by setting the
438
    `suboption_value_delimiter` class variable.
439
    """
440

441
    suboption: ClassVar[str]
10✔
442
    suboption_value_delimiter: ClassVar[str] = "="
10✔
443

444
    @final
10✔
445
    def buildctl_options(
10✔
446
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
447
    ) -> Iterator[str]:
NEW
448
        if self.value is not None:
×
NEW
449
            yield from (
×
450
                self.buildctl_option,
451
                f"{self.suboption}{self.suboption_value_delimiter}{self.value}",
452
            )
453

454

455
class BuildctlOptionFieldMultiValueMixin(StringSequenceField, BuildctlOptionsFieldMixin, ABC):
10✔
456
    """Inherit this mixin class to provide options in the form of `--flag=value1,value2` to
457
    `buildctl build`."""
458

459
    @final
10✔
460
    def buildctl_options(
10✔
461
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
462
    ) -> Iterator[str]:
NEW
463
        if self.value:
×
NEW
464
            yield f"{self.buildctl_option}={','.join(list(self.value))}"
×
465

466

467
class BuildctlLayeredOptionFieldMultiValueMixin(
10✔
468
    StringSequenceField, BuildctlOptionsFieldMixin, ABC
469
):
470
    """Inherit this mixin class to provide layered options in the form of `--flag
471
    suboption=value1,value2` to `buildctl build`.
472

473
    You can override the option-values delimiter (default is `=`) by setting the
474
    `suboption_value_delimiter` class variable.
475
    """
476

477
    suboption: ClassVar[str]
10✔
478
    suboption_value_delimiter: ClassVar[str] = "="
10✔
479

480
    @final
10✔
481
    def buildctl_options(
10✔
482
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
483
    ) -> Iterator[str]:
NEW
484
        if self.value:
×
NEW
485
            yield from (
×
486
                self.buildctl_option,
487
                f"{self.suboption}{self.suboption_value_delimiter}{','.join(list(self.value))}",
488
            )
489

490

491
class BuildctlOptionFlagFieldMixin(BoolField, BuildctlOptionsFieldMixin, ABC):
10✔
492
    """Inherit this mixin class to provide optional flags (i.e. add `--flag` only when the value is
493
    `True`) to `buildctl build`."""
494

495
    @final
10✔
496
    def buildctl_options(
10✔
497
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
498
    ) -> Iterator[str]:
NEW
499
        if self.value:
×
NEW
500
            yield f"{self.buildctl_option}"
×
501

502

503
class DockerImageTargetStageField(
10✔
504
    DockerBuildOptionFieldValueMixin, BuildctlLayeredOptionFieldValueMixin, StringField
505
):
506
    alias = "target_stage"
10✔
507
    help = help_text(
10✔
508
        """
509
        Specify target build stage, rather than building the entire `Dockerfile`.
510

511
        When using multi-stage build, you may name your stages, and can target them when building
512
        to only selectively build a certain stage. See also the `--docker-build-target-stage`
513
        option.
514

515
        Read more about [multi-stage Docker builds](https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage)
516
        """
517
    )
518
    docker_build_option = "--target"
10✔
519
    buildctl_option = "--opt"
10✔
520
    suboption = "target"
10✔
521

522
    @staticmethod
10✔
523
    def validate_options(options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
524
        # Defer to global option if set and matches a stage
NEW
525
        return options.build_target_stage not in context.stages
×
526

527

528
class DockerImageBuildImageLabelsOptionField(
10✔
529
    DockerBuildOptionMultiValueFieldMixin,
530
    BuildctlOptionMultiValueFieldMixin,
531
    DictStringToStringField,
532
):
533
    alias = "image_labels"
10✔
534
    help = help_text(
10✔
535
        f"""
536
        Provide image metadata.
537

538
        {_interpolation_help.format(kind="Label value")}
539

540
        See [Docker labels](https://docs.docker.com/config/labels-custom-metadata/#manage-labels-on-objects)
541
        for more information.
542
        """
543
    )
544
    docker_build_option = "--label"
10✔
545
    buildctl_option = "--opt"
10✔
546

547
    def docker_build_option_values(
10✔
548
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
549
    ) -> Iterator[str]:
550
        for label, value in (self.value or {}).items():
3✔
NEW
551
            yield f"{label}={value_formatter(value)}"
×
552

553
    def buildctl_option_values(
10✔
554
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
555
    ) -> Iterator[str]:
NEW
556
        for label, value in (self.value or {}).items():
×
NEW
557
            yield f"label:{label}={value_formatter(value)}"
×
558

559

560
class DockerImageBuildImageExtraHostsField(
10✔
561
    DockerBuildOptionMultiValueFieldMixin, DictStringToStringField
562
):
563
    alias = "extra_build_hosts"
10✔
564
    help = help_text(
10✔
565
        """
566
        Extra hosts entries to be added to a container's `/etc/hosts` file.
567

568
        Use `[docker].build_hosts` to set default host entries for all images.
569
        """
570
    )
571
    docker_build_option = "--add-host"
10✔
572

573
    def docker_build_option_values(
10✔
574
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
575
    ) -> Iterator[str]:
576
        if self.value:
3✔
NEW
577
            merged_values = {**docker.build_hosts, **self.value}
×
NEW
578
            for label, value in merged_values.items():
×
NEW
579
                yield f"{label}:{value_formatter(value)}"
×
580

581

582
class DockerImageBuildImageCacheToField(
10✔
583
    DockerBuildOptionFieldMultiValueDictMixin,
584
    BuildctlOptionFieldMultiValueDictMixin,
585
    DictStringToStringField,
586
):
587
    alias = "cache_to"
10✔
588
    help = help_text(
10✔
589
        f"""
590
        Export image build cache to an external cache destination.
591

592
        Note that Docker [supports](https://docs.docker.com/build/cache/backends/#multiple-caches)
593
        multiple cache sources - Pants will pass these as multiple `--cache_from` arguments to the
594
        Docker CLI. Docker will only use the first cache hit (i.e. the image exists) in the build.
595

596
        If you're using the legacy builder, this option is not supported.
597

598
        Example:
599

600
            docker_image(
601
                name="example-local-cache-backend",
602
                cache_to={{
603
                    "type": "local",
604
                    "dest": "/tmp/docker-cache/example"
605
                }},
606
                cache_from=[{{
607
                    "type": "local",
608
                    "src": "/tmp/docker-cache/example"
609
                }}]
610
            )
611

612
        {_interpolation_help.format(kind="Values")}
613
        """
614
    )
615
    docker_build_option = "--cache-to"
10✔
616
    buildctl_option = "--export-cache"
10✔
617

618
    @staticmethod
10✔
619
    def validate_options(options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
620
        return not options.build_no_cache
3✔
621

622

623
class DockerImageBuildImageCacheFromField(
10✔
624
    DockerBuildOptionFieldListOfMultiValueDictMixin,
625
    BuildctlOptionFieldListOfMultiValueDictMixin,
626
    ListOfDictStringToStringField,
627
):
628
    alias = "cache_from"
10✔
629
    help = help_text(
10✔
630
        f"""
631
        Use external cache sources when building the image.
632

633
        If you're using the legacy builder, this option is not supported.
634

635
        Example:
636

637
            docker_image(
638
                name="example-local-cache-backend",
639
                cache_to={{
640
                    "type": "local",
641
                    "dest": "/tmp/docker-cache/primary"
642
                }},
643
                cache_from=[
644
                    {{
645
                        "type": "local",
646
                        "src": "/tmp/docker-cache/primary"
647
                    }},
648
                    {{
649
                        "type": "local",
650
                        "src": "/tmp/docker-cache/secondary"
651
                    }}
652
                ]
653
            )
654

655
        {_interpolation_help.format(kind="Values")}
656
        """
657
    )
658
    docker_build_option = "--cache-from"
10✔
659
    buildctl_option = "--import-cache"
10✔
660

661
    @staticmethod
10✔
662
    def validate_options(options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
663
        return not options.build_no_cache
3✔
664

665

666
class DockerImageBuildImageOutputField(
10✔
667
    BuildctlOptionFieldMultiValueDictMixin,
668
    DockerBuildkitPassthroughFieldMixin,
669
    DictStringToStringField,
670
):
671
    alias = "output"
10✔
672
    default = FrozenDict({"type": "docker"})
10✔
673
    help = help_text(
10✔
674
        f"""
675
        Sets the export action for the build result.
676

677
        If you're using the legacy builder, this option is not supported.
678

679
        When using `pants publish` to publish Docker images to a registry, the output type
680
        must be 'docker', as `publish` expects that the built images exist in the local
681
        image store.
682

683
        {_interpolation_help.format(kind="Values")}
684
        """
685
    )
686
    buildctl_option = "--output"
10✔
687

688

689
class DockerImageBuildSecretsOptionField(
10✔
690
    AsyncFieldMixin,
691
    BuildctlOptionMultiValueFieldMixin,
692
    DockerBuildkitPassthroughFieldMixin,
693
    DictStringToStringField,
694
):
695
    alias = "secrets"
10✔
696
    help = help_text(
10✔
697
        """
698
        Secret files to expose to the build (only if BuildKit enabled).
699

700
        Secrets may use absolute paths, or paths relative to your build root, or the BUILD file
701
        if prefixed with `./`. Paths to your home directory will be automatically expanded.
702
        The id should be valid as used by the Docker build `--secret` option.
703
        See [Docker secrets](https://docs.docker.com/engine/swarm/secrets/) for more
704
        information.
705

706
        Example:
707

708
            docker_image(
709
                secrets={
710
                    "mysecret": "/var/secrets/some-secret",
711
                    "repo-secret": "src/proj/secrets/some-secret",
712
                    "home-dir-secret": "~/.config/some-secret",
713
                    "target-secret": "./secrets/some-secret",
714
                }
715
            )
716
        """
717
    )
718

719
    buildctl_option = "--secret"
10✔
720

721
    def buildctl_option_values(
10✔
722
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
723
    ) -> Iterator[str]:
724
        # os.path.join() discards preceding parts if encountering an abs path, e.g. if the secret
725
        # `path` is an absolute path, the `buildroot` and `spec_path` will not be considered.  Also,
726
        # an empty path part is ignored.
727
        for secret, path in (self.value or {}).items():
1✔
728
            full_path = os.path.join(
1✔
729
                get_buildroot(),
730
                self.address.spec_path if re.match(r"\.{1,2}/", path) else "",
731
                os.path.expanduser(path),
732
            )
733

734
            yield f"id={secret},src={os.path.normpath(full_path)}"
1✔
735

736

737
class DockerImageBuildSSHOptionField(
10✔
738
    BuildctlOptionMultiValueFieldMixin,
739
    DockerBuildkitPassthroughFieldMixin,
740
    StringSequenceField,
741
):
742
    alias = "ssh"
10✔
743
    default = ()
10✔
744
    help = help_text(
10✔
745
        """
746
        SSH agent socket or keys to expose to the build (only if BuildKit enabled)
747
        (format: `default|<id>[=<socket>|<key>[,<key>]]`)
748

749
        The exposed agent and/or keys can then be used in your `Dockerfile` by mounting them in
750
        your `RUN` instructions:
751

752
            RUN --mount=type=ssh ...
753

754
        See [Docker documentation](https://docs.docker.com/develop/develop-images/build_enhancements/#using-ssh-to-access-private-data-in-builds)
755
        for more information.
756
        """
757
    )
758

759
    buildctl_option = "--ssh"
10✔
760

761
    def buildctl_option_values(
10✔
762
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
763
    ) -> Iterator[str]:
NEW
764
        yield from cast(tuple[str, ...], self.value)
×
765

766

767
class DockerImageBuildPullOptionField(DockerBuildOptionFieldValueMixin, BoolField):
10✔
768
    alias = "pull"
10✔
769
    default = False
10✔
770
    help = help_text(
10✔
771
        """
772
        If true, then docker will always attempt to pull a newer version of the image.
773

774
        NOTE: This option cannot be used on images that build off of "transitive" base images
775
        referenced by address (i.e. `FROM path/to/your/base/Dockerfile`).
776
        """
777
    )
778
    docker_build_option = "--pull"
10✔
779

780

781
class DockerImageBuildSquashOptionField(DockerBuildOptionFlagFieldMixin):
10✔
782
    alias = "squash"
10✔
783
    default = False
10✔
784
    help = help_text(
10✔
785
        """
786
        If true, then docker will squash newly built layers into a single new layer.
787

788
        Note that this option is only supported on a Docker daemon with experimental features enabled.
789
        """
790
    )
791
    docker_build_option = "--squash"
10✔
792

793

794
class DockerImageBuildNetworkOptionField(DockerBuildOptionFieldValueMixin, StringField):
10✔
795
    alias = "build_network"
10✔
796
    default = None
10✔
797
    help = help_text(
10✔
798
        """
799
        Sets the networking mode for the run commands during build.
800
        Supported standard values are: bridge, host, none, and container:<name|id>.
801
        Any other value is taken as a custom network's name to which the container should connect to.
802
        """
803
    )
804
    docker_build_option = "--network"
10✔
805

806

807
class DockerImageBuildPlatformOptionField(
10✔
808
    DockerBuildOptionFieldMultiValueMixin,
809
    BuildctlLayeredOptionFieldMultiValueMixin,
810
    StringSequenceField,
811
):
812
    alias = "build_platform"
10✔
813
    default = None
10✔
814
    help = help_text(
10✔
815
        """
816
        Set the target platform(s) for the build.
817
        """
818
    )
819
    docker_build_option = "--platform"
10✔
820
    buildctl_option = "--opt"
10✔
821
    suboption = "platform"
10✔
822

823

824
class DockerImageRunExtraArgsField(StringSequenceField):
10✔
825
    alias: ClassVar[str] = "extra_run_args"
10✔
826
    default = ()
10✔
827
    help = help_text(
10✔
828
        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`"
829
    )
830

831

832
class DockerImageTarget(Target):
10✔
833
    alias = "docker_image"
10✔
834
    core_fields = (
10✔
835
        *COMMON_TARGET_FIELDS,
836
        DockerImageBuildArgsField,
837
        DockerImageDependenciesField,
838
        DockerImageSourceField,
839
        DockerImageInstructionsField,
840
        DockerImageContextRootField,
841
        DockerImageTagsField,
842
        DockerImageRegistriesField,
843
        DockerImageRepositoryField,
844
        DockerImageBuildImageLabelsOptionField,
845
        DockerImageBuildImageExtraHostsField,
846
        DockerImageBuildSecretsOptionField,
847
        DockerImageBuildSSHOptionField,
848
        DockerImageSkipPushField,
849
        DockerImageTargetStageField,
850
        DockerImageBuildPullOptionField,
851
        DockerImageBuildSquashOptionField,
852
        DockerImageBuildNetworkOptionField,
853
        DockerImageBuildPlatformOptionField,
854
        DockerImageBuildImageCacheToField,
855
        DockerImageBuildImageCacheFromField,
856
        DockerImageBuildImageOutputField,
857
        DockerImageRunExtraArgsField,
858
        OutputPathField,
859
        RestartableField,
860
    )
861
    help = help_text(
862
        """
863
        The `docker_image` target describes how to build and tag a Docker image.
864

865
        Any dependencies, as inferred or explicitly specified, will be included in the Docker
866
        build context, after being packaged if applicable.
867

868
        By default, it will use a Dockerfile from the same directory as the BUILD file this target
869
        is defined in. Point at another file with the `source` field, or use the `instructions`
870
        field to have the Dockerfile contents verbatim directly in the BUILD file.
871

872
        Dependencies on upstream/base images defined by another `docker_image` are inferred if
873
        referenced by a build argument with a default value of the target address.
874

875
        Example:
876

877
            # src/docker/downstream/Dockerfile
878
            ARG BASE=src/docker/upstream:image
879
            FROM $BASE
880
            ...
881

882
        """
883
    )
884

885

886
@union(in_scope_types=[EnvironmentName])
10✔
887
@dataclass(frozen=True)
10✔
888
class DockerImageTagsRequest:
10✔
889
    """A request to provide additional image tags."""
890

891
    target: Target
10✔
892

893
    @classmethod
10✔
894
    def is_applicable(cls, target: Target) -> bool:
10✔
895
        """Whether to provide additional tags for this target or not."""
896
        return True
1✔
897

898

899
class DockerImageTags(Collection[str]):
10✔
900
    """Additional image tags to apply to built Docker images."""
901

902

903
@rule(polymorphic=True)
10✔
904
async def get_docker_image_tags(
10✔
905
    req: DockerImageTagsRequest, env_name: EnvironmentName
906
) -> DockerImageTags:
907
    raise NotImplementedError()
×
908

909

910
class AllDockerImageTargets(Targets):
10✔
911
    pass
10✔
912

913

914
@rule
10✔
915
async def all_docker_targets(all_targets: AllTargets) -> AllDockerImageTargets:
10✔
916
    return AllDockerImageTargets(
6✔
917
        [tgt for tgt in all_targets if tgt.has_field(DockerImageSourceField)]
918
    )
919

920

921
def rules():
10✔
922
    return collect_rules()
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc