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

pantsbuild / pants / 23559966294

25 Mar 2026 07:27PM UTC coverage: 92.882% (-0.04%) from 92.918%
23559966294

Pull #23133

github

web-flow
Merge b6f2381e0 into 9b3c1562e
Pull Request #23133: Add buildctl engine

289 of 337 new or added lines in 13 files covered. (85.76%)

2 existing lines in 2 files now uncovered.

91640 of 98663 relevant lines covered (92.88%)

4.06 hits per line

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

92.86
/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, 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(Field, 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
4✔
237

238

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

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

249
    def docker_build_options(
10✔
250
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
251
    ) -> Iterator[str]:
252
        return self.compute_docker_build_options(
4✔
253
            self.value, docker=docker, value_formatter=value_formatter
254
        )
255

256

257
class DockerBuildOptionFieldValueMixin(DockerBuildOptionsFieldMixin, ABC):
10✔
258
    """Inherit this mixin class to provide unary options (i.e. option in the form of `--flag=value`)
259
    to `docker build`."""
260

261
    @classmethod
10✔
262
    @final
10✔
263
    def compute_docker_build_options(
10✔
264
        cls, value, *, docker: DockerOptions, value_formatter: OptionValueFormatter
265
    ) -> Iterator[str]:
266
        if value is not None:
4✔
267
            yield f"{cls.docker_build_option}={value}"
4✔
268

269

270
class DockerBuildOptionFieldMultiValueMixin(StringSequenceField, DockerBuildOptionsFieldMixin, ABC):
10✔
271
    """Inherit this mixin class to provide options in the form of `--flag=value1,value2` to `docker
272
    build`."""
273

274
    @classmethod
10✔
275
    @final
10✔
276
    def compute_docker_build_options(
10✔
277
        cls,
278
        value: tuple[str, ...] | None,
279
        *,
280
        docker: DockerOptions,
281
        value_formatter: OptionValueFormatter,
282
    ) -> Iterator[str]:
283
        if value:
4✔
284
            yield f"{cls.docker_build_option}={','.join(value)}"
1✔
285

286

287
class DockerBuildOptionFieldMultiValueDictMixin(
10✔
288
    DictStringToStringField, DockerBuildOptionsFieldMixin, ABC
289
):
290
    """Inherit this mixin class to provide options in the form of `--flag=key1=value1,key2=value2`
291
    to `docker build`."""
292

293
    @classmethod
10✔
294
    @final
10✔
295
    def compute_docker_build_options(
10✔
296
        cls,
297
        value: FrozenDict[str, str] | None,
298
        *,
299
        docker: DockerOptions,
300
        value_formatter: OptionValueFormatter,
301
    ) -> Iterator[str]:
302
        if value:
4✔
303
            yield f"{cls.docker_build_option}=" + ",".join(
1✔
304
                f"{key}={value_formatter(v)}" for key, v in value.items()
305
            )
306

307

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

313
    `--flag=key1=value1,key2=value2 --flag=key3=value3,key4=value4`
314
    """
315

316
    @classmethod
10✔
317
    @final
10✔
318
    def compute_docker_build_options(
10✔
319
        cls,
320
        value: tuple[FrozenDict[str, str]] | None,
321
        *,
322
        docker: DockerOptions,
323
        value_formatter: OptionValueFormatter,
324
    ) -> Iterator[str]:
325
        if value:
4✔
326
            for item in value:
1✔
327
                yield f"{cls.docker_build_option}=" + ",".join(
1✔
328
                    f"{key}={value_formatter(v)}" for key, v in item.items()
329
                )
330

331

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

336
    @classmethod
10✔
337
    @final
10✔
338
    def compute_docker_build_options(
10✔
339
        cls, value: bool, *, docker: DockerOptions, value_formatter: OptionValueFormatter
340
    ) -> Iterator[str]:
341
        if value:
4✔
342
            yield f"{cls.docker_build_option}"
1✔
343

344

345
class BuildctlOptionsFieldMixin(ValidateOptionsMixin, ABC):
10✔
346
    buildctl_option: ClassVar[str]
10✔
347

348
    @classmethod
10✔
349
    @abstractmethod
10✔
350
    def compute_buildctl_options(
10✔
351
        cls, value, *, docker: DockerOptions, value_formatter: OptionValueFormatter
352
    ) -> Iterator[str]:
353
        """Subclasses must implement this, to turn `value` into none, one or more option values."""
354

355
    def buildctl_options(
10✔
356
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
357
    ) -> Iterator[str]:
NEW
358
        return self.compute_buildctl_options(
×
359
            self.value, docker=docker, value_formatter=value_formatter
360
        )
361

362

363
class DockerBuildkitPassthroughFieldMixin(
10✔
364
    BuildctlOptionsFieldMixin, DockerBuildOptionsFieldMixin, ABC
365
):
366
    @classproperty
10✔
367
    def docker_build_option(cls) -> str:
10✔
NEW
368
        return cls.buildctl_option
×
369

370
    @classmethod
10✔
371
    def compute_docker_build_options(
10✔
372
        cls, value, *, docker: DockerOptions, value_formatter: OptionValueFormatter
373
    ) -> Iterator[str]:
374
        return cls.compute_buildctl_options(value, docker=docker, value_formatter=value_formatter)
4✔
375

376

377
class BuildctlOptionFieldMultiValueDictMixin(
10✔
378
    DictStringToStringField, BuildctlOptionsFieldMixin, ABC
379
):
380
    """Inherit this mixin class to provide options in the form of `--flag=key1=value1,key2=value2`
381
    to `buildctl build`."""
382

383
    @classmethod
10✔
384
    @final
10✔
385
    def compute_buildctl_options(
10✔
386
        cls,
387
        value: FrozenDict[str, str] | None,
388
        *,
389
        docker: DockerOptions,
390
        value_formatter: OptionValueFormatter,
391
    ) -> Iterator[str]:
392
        if value:
4✔
393
            yield f"{cls.buildctl_option}=" + ",".join(
4✔
394
                f"{key}={value_formatter(v)}" for key, v in value.items()
395
            )
396

397

398
class BuildctlOptionFieldListOfMultiValueDictMixin(
10✔
399
    ListOfDictStringToStringField, BuildctlOptionsFieldMixin, ABC
400
):
401
    """Inherit this mixin class to provide multiple key-value options to buildctl build:
402

403
    `--flag=key1=value1,key2=value2 --flag=key3=value3,key4=value4`
404
    """
405

406
    @classmethod
10✔
407
    @final
10✔
408
    def compute_buildctl_options(
10✔
409
        cls,
410
        value: tuple[FrozenDict[str, str], ...] | None,
411
        *,
412
        docker: DockerOptions,
413
        value_formatter: OptionValueFormatter,
414
    ) -> Iterator[str]:
NEW
415
        if value:
×
NEW
416
            for item in value:
×
NEW
417
                yield f"{cls.buildctl_option}=" + ",".join(
×
418
                    f"{key}={value_formatter(v)}" for key, v in item.items()
419
                )
420

421

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

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

434

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

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

443
    suboption: ClassVar[str]
10✔
444
    suboption_value_delimiter: ClassVar[str] = "="
10✔
445

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

455

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

460
    @classmethod
10✔
461
    @final
10✔
462
    def compute_buildctl_options(
10✔
463
        cls,
464
        value: tuple[str, ...] | None,
465
        *,
466
        docker: DockerOptions,
467
        value_formatter: OptionValueFormatter,
468
    ) -> Iterator[str]:
NEW
469
        if value:
×
NEW
470
            yield f"{cls.buildctl_option}={','.join(value)}"
×
471

472

473
class BuildctlLayeredOptionFieldMultiValueMixin(
10✔
474
    StringSequenceField, BuildctlOptionsFieldMixin, ABC
475
):
476
    """Inherit this mixin class to provide layered options in the form of `--flag
477
    suboption=value1,value2` to `buildctl build`.
478

479
    You can override the option-values delimiter (default is `=`) by setting the
480
    `suboption_value_delimiter` class variable.
481
    """
482

483
    suboption: ClassVar[str]
10✔
484
    suboption_value_delimiter: ClassVar[str] = "="
10✔
485

486
    @classmethod
10✔
487
    @final
10✔
488
    def compute_buildctl_options(
10✔
489
        cls,
490
        value: tuple[str, ...] | None,
491
        *,
492
        docker: DockerOptions,
493
        value_formatter: OptionValueFormatter,
494
    ) -> Iterator[str]:
NEW
495
        if value:
×
NEW
496
            yield cls.buildctl_option
×
NEW
497
            yield f"{cls.suboption}{cls.suboption_value_delimiter}{','.join(value)}"
×
498

499

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

504
    @classmethod
10✔
505
    @final
10✔
506
    def compute_buildctl_options(
10✔
507
        cls, value: bool, *, docker: DockerOptions, value_formatter: OptionValueFormatter
508
    ) -> Iterator[str]:
NEW
509
        if value:
×
NEW
510
            yield f"{cls.buildctl_option}"
×
511

512

513
class DockerImageTargetStageField(
10✔
514
    DockerBuildOptionFieldValueMixin, BuildctlLayeredOptionFieldValueMixin, StringField
515
):
516
    alias = "target_stage"
10✔
517
    help = help_text(
10✔
518
        """
519
        Specify target build stage, rather than building the entire `Dockerfile`.
520

521
        When using multi-stage build, you may name your stages, and can target them when building
522
        to only selectively build a certain stage. See also the `--docker-build-target-stage`
523
        option.
524

525
        Read more about [multi-stage Docker builds](https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage)
526
        """
527
    )
528
    docker_build_option = "--target"
10✔
529
    buildctl_option = "--opt"
10✔
530
    suboption = "target"
10✔
531

532
    @staticmethod
10✔
533
    def validate_options(options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
534
        # Defer to global option if set and matches a stage
535
        return options.build_target_stage not in context.stages
4✔
536

537

538
class DockerImageBuildImageLabelsOptionField(
10✔
539
    DockerBuildOptionsFieldMixin,
540
    BuildctlOptionsFieldMixin,
541
    DictStringToStringField,
542
):
543
    alias = "image_labels"
10✔
544
    help = help_text(
10✔
545
        f"""
546
        Provide image metadata.
547

548
        {_interpolation_help.format(kind="Label value")}
549

550
        See [Docker labels](https://docs.docker.com/config/labels-custom-metadata/#manage-labels-on-objects)
551
        for more information.
552
        """
553
    )
554
    docker_build_option = "--label"
10✔
555
    buildctl_option = "--opt"
10✔
556

557
    @classmethod
10✔
558
    def compute_docker_build_options(
10✔
559
        cls,
560
        value: FrozenDict[str, str] | None,
561
        *,
562
        docker: DockerOptions,
563
        value_formatter: OptionValueFormatter,
564
    ) -> Iterator[str]:
565
        for label, v in (value or {}).items():
4✔
566
            yield cls.docker_build_option
1✔
567
            yield f"{label}={value_formatter(v)}"
1✔
568

569
    @classmethod
10✔
570
    def compute_buildctl_options(
10✔
571
        cls,
572
        value: FrozenDict[str, str] | None,
573
        *,
574
        docker: DockerOptions,
575
        value_formatter: OptionValueFormatter,
576
    ) -> Iterator[str]:
NEW
577
        for label, v in (value or {}).items():
×
NEW
578
            yield cls.buildctl_option
×
NEW
579
            yield f"label:{label}={value_formatter(v)}"
×
580

581

582
class DockerImageBuildImageExtraHostsField(DockerBuildOptionsFieldMixin, DictStringToStringField):
10✔
583
    alias = "extra_build_hosts"
10✔
584
    help = help_text(
10✔
585
        """
586
        Extra hosts entries to be added to a container's `/etc/hosts` file.
587

588
        Use `[docker].build_hosts` to set default host entries for all images.
589
        """
590
    )
591
    docker_build_option = "--add-host"
10✔
592

593
    @classmethod
10✔
594
    def compute_docker_build_options(
10✔
595
        cls,
596
        value: FrozenDict[str, str] | None,
597
        *,
598
        docker: DockerOptions,
599
        value_formatter: OptionValueFormatter,
600
    ) -> Iterator[str]:
601
        if value:
4✔
602
            merged_values = {**docker.build_hosts, **value}
1✔
603
            for label, v in merged_values.items():
1✔
604
                yield cls.docker_build_option
1✔
605
                yield f"{label}:{value_formatter(v)}"
1✔
606

607

608
class DockerImageBuildImageCacheToField(
10✔
609
    DockerBuildOptionFieldMultiValueDictMixin,
610
    BuildctlOptionFieldMultiValueDictMixin,
611
    DictStringToStringField,
612
):
613
    alias = "cache_to"
10✔
614
    help = help_text(
10✔
615
        f"""
616
        Export image build cache to an external cache destination.
617

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

622
        If you're using the legacy builder, this option is not supported.
623

624
        Example:
625

626
            docker_image(
627
                name="example-local-cache-backend",
628
                cache_to={{
629
                    "type": "local",
630
                    "dest": "/tmp/docker-cache/example"
631
                }},
632
                cache_from=[{{
633
                    "type": "local",
634
                    "src": "/tmp/docker-cache/example"
635
                }}]
636
            )
637

638
        {_interpolation_help.format(kind="Values")}
639
        """
640
    )
641
    docker_build_option = "--cache-to"
10✔
642
    buildctl_option = "--export-cache"
10✔
643

644
    @staticmethod
10✔
645
    def validate_options(options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
646
        return not options.build_no_cache
4✔
647

648

649
class DockerImageBuildImageCacheFromField(
10✔
650
    DockerBuildOptionFieldListOfMultiValueDictMixin,
651
    BuildctlOptionFieldListOfMultiValueDictMixin,
652
    ListOfDictStringToStringField,
653
):
654
    alias = "cache_from"
10✔
655
    help = help_text(
10✔
656
        f"""
657
        Use external cache sources when building the image.
658

659
        If you're using the legacy builder, this option is not supported.
660

661
        Example:
662

663
            docker_image(
664
                name="example-local-cache-backend",
665
                cache_to={{
666
                    "type": "local",
667
                    "dest": "/tmp/docker-cache/primary"
668
                }},
669
                cache_from=[
670
                    {{
671
                        "type": "local",
672
                        "src": "/tmp/docker-cache/primary"
673
                    }},
674
                    {{
675
                        "type": "local",
676
                        "src": "/tmp/docker-cache/secondary"
677
                    }}
678
                ]
679
            )
680

681
        {_interpolation_help.format(kind="Values")}
682
        """
683
    )
684
    docker_build_option = "--cache-from"
10✔
685
    buildctl_option = "--import-cache"
10✔
686

687
    @staticmethod
10✔
688
    def validate_options(options: DockerOptions, context: DockerBuildContext) -> bool:
10✔
689
        return not options.build_no_cache
4✔
690

691

692
class DockerImageBuildImageOutputField(
10✔
693
    BuildctlOptionFieldMultiValueDictMixin,
694
    DockerBuildkitPassthroughFieldMixin,
695
    DictStringToStringField,
696
):
697
    alias = "output"
10✔
698
    default = FrozenDict({"type": "docker"})
10✔
699
    help = help_text(
10✔
700
        f"""
701
        Sets the export action for the build result.
702

703
        If you're using the legacy builder, this option is not supported.
704

705
        When using `pants publish` to publish Docker images to a registry, the output type
706
        must be 'docker', as `publish` expects that the built images exist in the local
707
        image store.
708

709
        {_interpolation_help.format(kind="Values")}
710
        """
711
    )
712
    buildctl_option = "--output"
10✔
713

714

715
class DockerImageBuildSecretsOptionField(
10✔
716
    AsyncFieldMixin,
717
    ValidateOptionsMixin,
718
    DictStringToStringField,
719
):
720
    alias = "secrets"
10✔
721
    help = help_text(
10✔
722
        """
723
        Secret files to expose to the build (only if BuildKit enabled).
724

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

731
        Example:
732

733
            docker_image(
734
                secrets={
735
                    "mysecret": "/var/secrets/some-secret",
736
                    "repo-secret": "src/proj/secrets/some-secret",
737
                    "home-dir-secret": "~/.config/some-secret",
738
                    "target-secret": "./secrets/some-secret",
739
                }
740
            )
741
        """
742
    )
743

744
    buildctl_option = "--secret"
10✔
745
    docker_build_option = "--secret"
10✔
746

747
    def buildctl_options(
10✔
748
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
749
    ) -> Iterator[str]:
750
        # os.path.join() discards preceding parts if encountering an abs path, e.g. if the secret
751
        # `path` is an absolute path, the `buildroot` and `spec_path` will not be considered.  Also,
752
        # an empty path part is ignored.
753
        for secret, path in (self.value or {}).items():
5✔
754
            full_path = os.path.join(
2✔
755
                get_buildroot(),
756
                self.address.spec_path if re.match(r"\.{1,2}/", path) else "",
757
                os.path.expanduser(path),
758
            )
759
            yield self.buildctl_option
2✔
760
            yield f"id={secret},src={os.path.normpath(full_path)}"
2✔
761

762
    def docker_build_options(
10✔
763
        self, *, docker: DockerOptions, value_formatter: OptionValueFormatter
764
    ) -> Iterator[str]:
765
        return self.buildctl_options(docker=docker, value_formatter=value_formatter)
4✔
766

767

768
class DockerImageBuildSSHOptionField(
10✔
769
    DockerBuildkitPassthroughFieldMixin,
770
    StringSequenceField,
771
):
772
    alias = "ssh"
10✔
773
    default = ()
10✔
774
    help = help_text(
10✔
775
        """
776
        SSH agent socket or keys to expose to the build (only if BuildKit enabled)
777
        (format: `default|<id>[=<socket>|<key>[,<key>]]`)
778

779
        The exposed agent and/or keys can then be used in your `Dockerfile` by mounting them in
780
        your `RUN` instructions:
781

782
            RUN --mount=type=ssh ...
783

784
        See [Docker documentation](https://docs.docker.com/develop/develop-images/build_enhancements/#using-ssh-to-access-private-data-in-builds)
785
        for more information.
786
        """
787
    )
788

789
    buildctl_option = "--ssh"
10✔
790

791
    @classmethod
10✔
792
    def compute_buildctl_options(
10✔
793
        cls, value: tuple[str, ...], *, docker: DockerOptions, value_formatter: OptionValueFormatter
794
    ) -> Iterator[str]:
795
        for v in value:
4✔
796
            yield cls.buildctl_option
1✔
797
            yield v
1✔
798

799

800
class DockerImageBuildPullOptionField(DockerBuildOptionFieldValueMixin, BoolField):
10✔
801
    alias = "pull"
10✔
802
    default = False
10✔
803
    help = help_text(
10✔
804
        """
805
        If true, then docker will always attempt to pull a newer version of the image.
806

807
        NOTE: This option cannot be used on images that build off of "transitive" base images
808
        referenced by address (i.e. `FROM path/to/your/base/Dockerfile`).
809
        """
810
    )
811
    docker_build_option = "--pull"
10✔
812

813

814
class DockerImageBuildSquashOptionField(DockerBuildOptionFlagFieldMixin):
10✔
815
    alias = "squash"
10✔
816
    default = False
10✔
817
    help = help_text(
10✔
818
        """
819
        If true, then docker will squash newly built layers into a single new layer.
820

821
        Note that this option is only supported on a Docker daemon with experimental features enabled.
822
        """
823
    )
824
    docker_build_option = "--squash"
10✔
825

826

827
class DockerImageBuildNetworkOptionField(DockerBuildOptionFieldValueMixin, StringField):
10✔
828
    alias = "build_network"
10✔
829
    default = None
10✔
830
    help = help_text(
10✔
831
        """
832
        Sets the networking mode for the run commands during build.
833
        Supported standard values are: bridge, host, none, and container:<name|id>.
834
        Any other value is taken as a custom network's name to which the container should connect to.
835
        """
836
    )
837
    docker_build_option = "--network"
10✔
838

839

840
class DockerImageBuildPlatformOptionField(
10✔
841
    DockerBuildOptionFieldMultiValueMixin,
842
    BuildctlLayeredOptionFieldMultiValueMixin,
843
    StringSequenceField,
844
):
845
    alias = "build_platform"
10✔
846
    default = None
10✔
847
    help = help_text(
10✔
848
        """
849
        Set the target platform(s) for the build.
850
        """
851
    )
852
    docker_build_option = "--platform"
10✔
853
    buildctl_option = "--opt"
10✔
854
    suboption = "platform"
10✔
855

856

857
class DockerImageRunExtraArgsField(StringSequenceField):
10✔
858
    alias: ClassVar[str] = "extra_run_args"
10✔
859
    default = ()
10✔
860
    help = help_text(
10✔
861
        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`"
862
    )
863

864

865
class DockerImageTarget(Target):
10✔
866
    alias = "docker_image"
10✔
867
    core_fields = (
10✔
868
        *COMMON_TARGET_FIELDS,
869
        DockerImageBuildArgsField,
870
        DockerImageDependenciesField,
871
        DockerImageSourceField,
872
        DockerImageInstructionsField,
873
        DockerImageContextRootField,
874
        DockerImageTagsField,
875
        DockerImageRegistriesField,
876
        DockerImageRepositoryField,
877
        DockerImageBuildImageLabelsOptionField,
878
        DockerImageBuildImageExtraHostsField,
879
        DockerImageBuildSecretsOptionField,
880
        DockerImageBuildSSHOptionField,
881
        DockerImageSkipPushField,
882
        DockerImageTargetStageField,
883
        DockerImageBuildPullOptionField,
884
        DockerImageBuildSquashOptionField,
885
        DockerImageBuildNetworkOptionField,
886
        DockerImageBuildPlatformOptionField,
887
        DockerImageBuildImageCacheToField,
888
        DockerImageBuildImageCacheFromField,
889
        DockerImageBuildImageOutputField,
890
        DockerImageRunExtraArgsField,
891
        OutputPathField,
892
        RestartableField,
893
    )
894
    help = help_text(
895
        """
896
        The `docker_image` target describes how to build and tag a Docker image.
897

898
        Any dependencies, as inferred or explicitly specified, will be included in the Docker
899
        build context, after being packaged if applicable.
900

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

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

908
        Example:
909

910
            # src/docker/downstream/Dockerfile
911
            ARG BASE=src/docker/upstream:image
912
            FROM $BASE
913
            ...
914

915
        """
916
    )
917

918

919
@union(in_scope_types=[EnvironmentName])
10✔
920
@dataclass(frozen=True)
10✔
921
class DockerImageTagsRequest:
10✔
922
    """A request to provide additional image tags."""
923

924
    target: Target
10✔
925

926
    @classmethod
10✔
927
    def is_applicable(cls, target: Target) -> bool:
10✔
928
        """Whether to provide additional tags for this target or not."""
929
        return True
1✔
930

931

932
class DockerImageTags(Collection[str]):
10✔
933
    """Additional image tags to apply to built Docker images."""
934

935

936
@rule(polymorphic=True)
10✔
937
async def get_docker_image_tags(
10✔
938
    req: DockerImageTagsRequest, env_name: EnvironmentName
939
) -> DockerImageTags:
940
    raise NotImplementedError()
×
941

942

943
class AllDockerImageTargets(Targets):
10✔
944
    pass
10✔
945

946

947
@rule
10✔
948
async def all_docker_targets(all_targets: AllTargets) -> AllDockerImageTargets:
10✔
949
    return AllDockerImageTargets(
6✔
950
        [tgt for tgt in all_targets if tgt.has_field(DockerImageSourceField)]
951
    )
952

953

954
def rules():
10✔
955
    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