Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Container variables are read by the host-side tasks unless their description exp
| `WORKDIR_PATH` | Home and working directory for the non-root `py` user; defaults to `/workspace` |
| `CONTAINER_APT_PACKAGES` | Space-delimited APT packages installed in the runtime stage |
| `CONTAINER_CUSTOM_ENTRYPOINT` | `[project.scripts]` key selected as the image entrypoint command |
| `CONTAINER_DOCKER_BUILD_ARGS` | Shell-tokenized arguments passed directly to `docker build`; task arguments after `--` take precedence |
| `CONTAINER_DOCKER_BUILD_OPTIONS` | Shell-tokenized options passed directly to `docker build`; task options after `--` take precedence |
| `CONTAINER_DOCKERFILE_PATH` | Project-owned application Dockerfile used by `build-image`, `build`, and `release` instead of the bundled template |
| `CONTAINER_DOCKERFILE_HOOK_PATH` | Executable host script that modifies the selected Dockerfile before the build |
| `CONTAINER_PRUNE_KEEP` | Prior images kept after a build; `-1` disables pruning, `0` keeps only the latest, and `N` keeps the latest plus `N` prior images |
Expand Down
13 changes: 11 additions & 2 deletions docs/tasks/container-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ poe build --dockerfile-path docker/Dockerfile.ci
poe release --dockerfile-path docker/Dockerfile.release
```

The project root remains the Docker build context. Standard image naming, version and commit tags, metadata build arguments, `CONTAINER_DOCKER_BUILD_ARGS`, Dockerfile hooks, image pruning, and release-time image pushing continue to work. A normal custom build does not select a target stage, so the Dockerfile does not need the bundled `runtime` stage. A custom debug build selects a stage named `debug`.
The project root remains the Docker build context. Standard image naming, version and commit tags, metadata build arguments, `CONTAINER_DOCKER_BUILD_OPTIONS`, Dockerfile hooks, image pruning, and release-time image pushing continue to work. A normal custom build does not select a target stage, so the Dockerfile does not need the bundled `runtime` stage. A custom debug build selects a stage named `debug`.

Settings that render content into the bundled template do not alter a project-owned Dockerfile. This includes `CONTAINER_APT_PACKAGES`, `CONTAINER_CUSTOM_ENTRYPOINT`, `CONTAINER_ENV`, extensions, and dependency-image mappings. Declare equivalent instructions in the project-owned Dockerfile when they are needed. BuildKit secrets derived from `UV_INDEX_*` credentials remain available as Docker build secrets, but the custom Dockerfile must mount them explicitly.

Expand Down Expand Up @@ -393,7 +393,16 @@ Arguments following Poe's `--` separator are passed directly to `docker build` w
poe build-image --single-arch -- --secret id=pip_conf,env=PIP_CONF
```

Set `CONTAINER_DOCKER_BUILD_ARGS` to persist the same options using shell quoting rules. Arguments supplied after `--` replace this setting for that invocation. Managed arguments are emitted before these native Docker arguments, so an explicit option such as `--build-arg WORKDIR_PATH=/app` can override its managed value.
Set `CONTAINER_DOCKER_BUILD_OPTIONS` to persist the same options using shell quoting rules. Options supplied after `--` replace this setting for that invocation. Managed build arguments are emitted before these native Docker options, so an explicit option such as `--build-arg WORKDIR_PATH=/app` can override its managed value.

For project-specific defaults, declare the options in Poe's environment. A name-only `--build-arg` reads its value from the current environment, while `env=` explicitly selects the host variable used for a BuildKit secret.

```toml
[tool.poe.env]
CONTAINER_DOCKER_BUILD_OPTIONS = "--secret id=MY_TOKEN,env=MY_TOKEN --build-arg MY_SETTING"
```

The Dockerfile must consume `MY_TOKEN` with a secret mount. Do not pass sensitive values through `--build-arg`, because build arguments are not designed to protect secrets.

See [Container settings](../configuration.md#container-settings) for the complete settings reference.

Expand Down
4 changes: 2 additions & 2 deletions docs/tasks/reference/build-deps-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Build only the container dependency collector image for this project.
## Usage

```shell
poe build-deps-image [DOCKER_BUILD_ARGS...] [--no-cache] [--plain] [--single-arch]
poe build-deps-image [DOCKER_BUILD_OPTIONS...] [--no-cache] [--plain] [--single-arch]
```

## Arguments

| Argument | Type | Description | Default |
| - | - | - | - |
| `docker_build_args...` | `string, repeatable` | Additional arguments passed directly to `docker build`. Provide them after the task's `--` separator. Overrides `CONTAINER_DOCKER_BUILD_ARGS` when provided. | — |
| `docker_build_options...` | `string, repeatable` | Additional options passed directly to `docker build`. Provide them after the task's `--` separator. Overrides `CONTAINER_DOCKER_BUILD_OPTIONS` when provided. | — |
| `--no-cache` | `boolean` | Do not use cache when building the deps image. | `false` |
| `--plain` | `boolean` | Do not pretty-print output. | `false` |
| `--single-arch` | `boolean` | Build images for a single architecture. | `false` |
4 changes: 2 additions & 2 deletions docs/tasks/reference/build-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Build the container image using a project-owned or bundled Dockerfile.
## Usage

```shell
poe build-image [DOCKER_BUILD_ARGS...] [--debug] [--no-cache] [--plain] [--single-arch] [--dockerfile-path DOCKERFILE_PATH] [--dockerfile-hook-path DOCKERFILE_HOOK_PATH] [--container-env CONTAINER_ENV...] [--container-envfile CONTAINER_ENVFILE...]
poe build-image [DOCKER_BUILD_OPTIONS...] [--debug] [--no-cache] [--plain] [--single-arch] [--dockerfile-path DOCKERFILE_PATH] [--dockerfile-hook-path DOCKERFILE_HOOK_PATH] [--container-env CONTAINER_ENV...] [--container-envfile CONTAINER_ENVFILE...]
```

## Arguments

| Argument | Type | Description | Default |
| - | - | - | - |
| `docker_build_args...` | `string, repeatable` | Additional arguments passed directly to `docker build`. Provide them after the task's `--` separator. Overrides `CONTAINER_DOCKER_BUILD_ARGS` when provided. | — |
| `docker_build_options...` | `string, repeatable` | Additional options passed directly to `docker build`. Provide them after the task's `--` separator. Overrides `CONTAINER_DOCKER_BUILD_OPTIONS` when provided. | — |
| `--debug` | `boolean` | Build the debug image. | `false` |
| `--no-cache` | `boolean` | Do not use cache when building the image. | `false` |
| `--plain` | `boolean` | Do not pretty-print output. | `false` |
Expand Down
24 changes: 12 additions & 12 deletions src/common_python_tasks/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _build_docker_build_command(
plain: bool,
target: BuildStage | None = None,
tags: Sequence[str] | None = None,
docker_build_args: Sequence[str] | None = None,
docker_build_options: Sequence[str] | None = None,
) -> list[str]:
build_cmd = [
"docker",
Expand All @@ -123,8 +123,8 @@ def _build_docker_build_command(
build_cmd += ["-t", tag]
if plain:
build_cmd += ["--progress", "plain"]
if docker_build_args:
build_cmd += [str(arg) for arg in docker_build_args]
if docker_build_options:
build_cmd += [str(option) for option in docker_build_options]
build_cmd.append(str(context_path))

return build_cmd
Expand Down Expand Up @@ -180,7 +180,7 @@ def render_build_image(
omit_target: bool = False,
image_name: str | None = None,
extra_build_args: dict[str, str] | None = None,
docker_build_args: Sequence[str] | None = None,
docker_build_options: Sequence[str] | None = None,
dockerfile_hook_path: Path | None = None,
keep_generated_files: bool = True,
) -> RenderedBuildPlan:
Expand All @@ -197,7 +197,7 @@ def render_build_image(
omit_target: Whether to omit the target stage from the command.
image_name: Optional image name override.
extra_build_args: Extra build-time arguments.
docker_build_args: Additional arguments passed directly to `docker build`.
docker_build_options: Additional options passed directly to `docker build`.
dockerfile_hook_path: Optional executable script path that can mutate
the generated Dockerfile in-place before build.
keep_generated_files: Whether to leave the generated Dockerfile in place.
Expand Down Expand Up @@ -338,7 +338,7 @@ def render_build_image(
plain,
target=None if omit_target else target,
tags=all_tags,
docker_build_args=docker_build_args,
docker_build_options=docker_build_options,
)
command_display = " ".join(quote(str(arg)) for arg in build_cmd)
return RenderedBuildPlan(
Expand Down Expand Up @@ -473,7 +473,7 @@ def build_deps_image(
plain: bool = False,
single_arch: bool = False,
extra_build_args: dict[str, str] | None = None,
docker_build_args: Sequence[str] | None = None,
docker_build_options: Sequence[str] | None = None,
cache_id_suffix: str = "",
) -> str:
"""Build the dependency collector image and return its full tag.
Expand All @@ -486,7 +486,7 @@ def build_deps_image(
plain: If `True`, use plain output mode for the Docker build.
single_arch: If `True`, build the image for the current host architecture only.
extra_build_args: Additional build arguments for the Docker build as a dictionary of `KEY: VALUE` pairs.
docker_build_args: Additional arguments passed directly to `docker build`.
docker_build_options: Additional options passed directly to `docker build`.
cache_id_suffix: A suffix to append to the cache ID.

Returns:
Expand Down Expand Up @@ -574,7 +574,7 @@ def build_deps_image(
no_cache,
plain,
tags=[full_tag],
docker_build_args=docker_build_args,
docker_build_options=docker_build_options,
)

LOGGER.info("Building deps image: %s", full_tag)
Expand All @@ -597,7 +597,7 @@ def build_image(
omit_target: bool = False,
image_name: str | None = None,
extra_build_args: dict[str, str] | None = None,
docker_build_args: Sequence[str] | None = None,
docker_build_options: Sequence[str] | None = None,
dockerfile_hook_path: Path | None = None,
) -> tuple[str, str]:
"""Build the primary image and return its version and commit tags.
Expand All @@ -613,7 +613,7 @@ def build_image(
omit_target: If `True`, omit the target stage.
image_name: Name of the image to build.
extra_build_args: Additional build arguments for the Docker build as a dictionary of `KEY: VALUE` pairs.
docker_build_args: Additional arguments passed directly to `docker build`.
docker_build_options: Additional options passed directly to `docker build`.
dockerfile_hook_path: Optional executable script path that can mutate
the generated Dockerfile in-place before build.

Expand All @@ -636,7 +636,7 @@ def build_image(
omit_target=omit_target,
image_name=image_name,
extra_build_args=extra_build_args,
docker_build_args=docker_build_args,
docker_build_options=docker_build_options,
dockerfile_hook_path=dockerfile_hook_path,
keep_generated_files=True,
)
Expand Down
35 changes: 19 additions & 16 deletions src/common_python_tasks/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ def uv_index_secret_mounts(credentials: list[dict[str, str | None]]) -> list[str
]


def uv_index_secret_build_args(credentials: list[dict[str, str | None]]) -> list[str]:
"""Return `docker build --secret` arg pairs for UV index credentials.
def uv_index_secret_build_options(
credentials: list[dict[str, str | None]],
) -> list[str]:
"""Return `docker build --secret` option pairs for UV index credentials.

Args:
credentials: Output of `collect_uv_index_credentials`.

Returns:
A flat list of `--secret` flag-value pairs for the docker build command.
A flat list of `--secret` option-value pairs for the Docker build command.
"""
return [
item
Expand Down Expand Up @@ -131,13 +133,13 @@ def get_python_variant() -> str:
def inject_auto_build_args_from_env(
build_args: dict[str, str] | None,
) -> dict[str, str]:
"""Inject configured environment variables into managed Docker build args.
"""Inject configured environment variables into managed Dockerfile build arguments.

Environment-driven build args are only injected if they are set, and never
override arguments already managed by the image builder.

Args:
build_args: Existing managed Docker build arguments.
build_args: Existing managed Dockerfile build arguments.

Returns:
A build-arg dictionary with auto-injected env values merged in.
Expand Down Expand Up @@ -337,28 +339,29 @@ def split_colon_delimited_values(value: str) -> list[str]:
return split_delimited_values(value, separators=":", allow_whitespace=True)


def resolve_container_docker_build_args(
cli_docker_build_args: tuple[str, ...], env_docker_build_args: str | None
def resolve_container_docker_build_options(
cli_docker_build_options: tuple[str, ...],
env_docker_build_options: str | None,
) -> list[str]:
"""Resolve arguments passed directly to `docker build`.
"""Resolve options passed directly to `docker build`.

Args:
cli_docker_build_args: Free arguments provided after the task's `--`
cli_docker_build_options: Free options provided after the task's `--`
separator.
env_docker_build_args: Shell-tokenized arguments from the environment.
env_docker_build_options: Shell-tokenized options from the environment.

Returns:
The CLI arguments when provided, otherwise the environment arguments.
The CLI options when provided, otherwise the environment options.
"""
if cli_docker_build_args:
return list(cli_docker_build_args)
if not env_docker_build_args:
if cli_docker_build_options:
return list(cli_docker_build_options)
if not env_docker_build_options:
return []

try:
return shlex.split(env_docker_build_args)
return shlex.split(env_docker_build_options)
except ValueError as error:
utils.fatal(f"Invalid CONTAINER_DOCKER_BUILD_ARGS: {error}")
utils.fatal(f"Invalid CONTAINER_DOCKER_BUILD_OPTIONS: {error}")


def _resolve_optional_file_path(
Expand Down
46 changes: 23 additions & 23 deletions src/common_python_tasks/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ def docs_serve(

@tasks.script(tags=["containers", "build"])
def build_image(
*docker_build_args: str,
*docker_build_options: str,
debug: bool = False,
no_cache: bool = False,
plain: bool = False,
Expand All @@ -503,9 +503,9 @@ def build_image(
"""Build the container image using a project-owned or bundled Dockerfile.

Args:
*docker_build_args: Additional arguments passed directly to `docker build`.
*docker_build_options: Additional options passed directly to `docker build`.
Provide them after the task's `--` separator. Overrides
`CONTAINER_DOCKER_BUILD_ARGS` when provided.
`CONTAINER_DOCKER_BUILD_OPTIONS` when provided.
debug: Build the debug image.
no_cache: Do not use cache when building the image.
plain: Do not pretty-print output.
Expand Down Expand Up @@ -538,12 +538,12 @@ def build_image(
parse_container_deps_source,
parse_container_extensions,
render_container_deps_move_script,
resolve_container_docker_build_args,
resolve_container_docker_build_options,
resolve_container_dockerfile_hook_path,
resolve_container_dockerfile_path,
resolve_extension_build_context,
resolve_extension_content,
uv_index_secret_build_args,
uv_index_secret_build_options,
uv_index_secret_mounts,
)
from .project import (
Expand Down Expand Up @@ -578,19 +578,19 @@ def build_image(
if (context := resolve_extension_build_context(desc)) is not None
]

resolved_docker_build_args = resolve_container_docker_build_args(
docker_build_args,
os.getenv("CONTAINER_DOCKER_BUILD_ARGS"),
resolved_docker_build_options = resolve_container_docker_build_options(
docker_build_options,
os.getenv("CONTAINER_DOCKER_BUILD_OPTIONS"),
)
uv_credentials = collect_uv_index_credentials()
if uv_credentials:
LOGGER.debug(
"Passing UV index credentials for: %s",
", ".join(c["index_name"] for c in uv_credentials),
)
resolved_docker_build_args = uv_index_secret_build_args(uv_credentials) + list(
resolved_docker_build_args
)
resolved_docker_build_options = uv_index_secret_build_options(
uv_credentials
) + list(resolved_docker_build_options)
resolved_dockerfile_hook_path = resolve_container_dockerfile_hook_path(
dockerfile_hook_path,
os.getenv("CONTAINER_DOCKERFILE_HOOK_PATH"),
Expand All @@ -609,7 +609,7 @@ def build_image(
single_arch=single_arch,
omit_target=not debug,
extra_build_args=top_level_build_args or None,
docker_build_args=resolved_docker_build_args,
docker_build_options=resolved_docker_build_options,
dockerfile_hook_path=resolved_dockerfile_hook_path,
)
_prune_container_images(version_tag, commit_tag)
Expand Down Expand Up @@ -713,7 +713,7 @@ def build_image(

if deps_content or deps_dockerfile_path:
deps_image_tag = build_deps_image_task(
*resolved_docker_build_args,
*resolved_docker_build_options,
no_cache=no_cache,
plain=plain,
single_arch=single_arch,
Expand Down Expand Up @@ -746,8 +746,8 @@ def build_image(
plain=plain,
single_arch=single_arch,
extra_build_args=merged_build_args or None,
docker_build_args=[
*resolved_docker_build_args,
docker_build_options=[
*resolved_docker_build_options,
*(
item
for name, path in extension_build_contexts
Expand All @@ -768,17 +768,17 @@ def build_image(

@tasks.script(task_name="build-deps-image", tags=["containers", "build"])
def build_deps_image_task(
*docker_build_args: str,
*docker_build_options: str,
no_cache: bool = False,
plain: bool = False,
single_arch: bool = False,
) -> None:
"""Build only the container dependency collector image for this project.

Args:
*docker_build_args: Additional arguments passed directly to `docker build`.
*docker_build_options: Additional options passed directly to `docker build`.
Provide them after the task's `--` separator. Overrides
`CONTAINER_DOCKER_BUILD_ARGS` when provided.
`CONTAINER_DOCKER_BUILD_OPTIONS` when provided.
no_cache: Do not use cache when building the deps image.
plain: Do not pretty-print output.
single_arch: Build images for a single architecture.
Expand All @@ -791,7 +791,7 @@ def build_deps_image_task(
get_cache_id_suffix,
inject_auto_build_args_from_env,
parse_container_deps_source,
resolve_container_docker_build_args,
resolve_container_docker_build_options,
)
from .utils import fatal

Expand All @@ -801,9 +801,9 @@ def build_deps_image_task(
"No container dependency source found. Set CONTAINER_DEPS_CONTENT or CONTAINER_DEPS_FILE."
)

resolved_docker_build_args = resolve_container_docker_build_args(
docker_build_args,
os.getenv("CONTAINER_DOCKER_BUILD_ARGS"),
resolved_docker_build_options = resolve_container_docker_build_options(
docker_build_options,
os.getenv("CONTAINER_DOCKER_BUILD_OPTIONS"),
)
extra_build_args = inject_auto_build_args_from_env({})
cache_id_suffix = get_cache_id_suffix(no_cache)
Expand All @@ -817,7 +817,7 @@ def build_deps_image_task(
plain=plain,
single_arch=single_arch,
extra_build_args=extra_build_args or None,
docker_build_args=resolved_docker_build_args,
docker_build_options=resolved_docker_build_options,
cache_id_suffix=cache_id_suffix,
)

Expand Down
Loading
Loading