Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
81dfce0
Add payload accessibility
Jrice1317 Apr 20, 2026
749abd4
First pass
Jrice1317 Apr 22, 2026
d60e4b3
Revert shar changes
Jrice1317 Apr 22, 2026
c3524ba
Fix logic with building on non-native platforms
Jrice1317 Apr 22, 2026
df67edf
Add mamba logic
Jrice1317 Apr 22, 2026
b71c8f9
Require base image to be provided in construct.yaml
Jrice1317 May 6, 2026
ec455fa
Update docker_build.py
Jrice1317 May 6, 2026
1fd5f98
Use existing vars in template
Jrice1317 May 6, 2026
5028672
Add docker as installer type
Jrice1317 May 6, 2026
487d802
Add example construct.yaml for tests
Jrice1317 May 6, 2026
c46044a
Add test
Jrice1317 May 6, 2026
ae648a5
Add clean command
Jrice1317 May 6, 2026
539500c
Call proper docker command in test
Jrice1317 May 6, 2026
43ca213
Fix pre-commit errors
Jrice1317 May 6, 2026
0bf8de5
Add docstring to beginning of file
Jrice1317 May 6, 2026
9f20cbe
Use schema vars properly
Jrice1317 May 6, 2026
65f9fd8
Use correct image name in test
Jrice1317 May 6, 2026
4fd0868
Update docs
Jrice1317 May 6, 2026
8b10804
Add news file
Jrice1317 May 6, 2026
01a9982
Do not generate file extension .docker
Jrice1317 May 6, 2026
d86ee52
Fix typos
Jrice1317 May 6, 2026
5784073
Always use sh for docker
Jrice1317 May 6, 2026
b2ff9d5
Pre-commit fix
Jrice1317 May 6, 2026
f97e163
Remove docker from os_allowed
Jrice1317 May 6, 2026
15a8b1f
Regenerate schema
Jrice1317 May 6, 2026
a5704bb
Add docker_build to schema
Jrice1317 May 6, 2026
8db9e96
Make whitespace adjustments
Jrice1317 May 8, 2026
6d1d118
Fix logic regarding base image requirement
Jrice1317 May 8, 2026
f65ecf3
Make image portable
Jrice1317 May 8, 2026
989fb30
Update wording
Jrice1317 May 8, 2026
7338b72
Update docs
Jrice1317 May 8, 2026
120d135
Revert to using one path
Jrice1317 May 8, 2026
be48dd1
Revert back to docker load
Jrice1317 May 8, 2026
18b7001
Add output
Jrice1317 May 8, 2026
583191d
Apply suggestions from code review
Jrice1317 May 14, 2026
b047963
Update logic
Jrice1317 May 14, 2026
80ee0a7
Merge branch 'docker-implementation' of https://github.com/Jrice1317/…
Jrice1317 May 14, 2026
2dbe4eb
Change wording in schema
Jrice1317 May 14, 2026
c1876f5
Be more descriptive
Jrice1317 May 15, 2026
c0ab7cb
Be more generalized
Jrice1317 May 15, 2026
963126c
Use multiline block
Jrice1317 May 15, 2026
40475f9
Refine logic
Jrice1317 May 15, 2026
9fea3d4
Move check to main if docker installed for building image
Jrice1317 May 15, 2026
1720895
Change docker_build to docker_image
Jrice1317 May 15, 2026
8735a9a
Expand tests
Jrice1317 May 15, 2026
56957c2
Pre-commit fixes
Jrice1317 May 15, 2026
ceb9a3c
Change logic
Jrice1317 May 15, 2026
511ade6
Update docs
Jrice1317 May 15, 2026
c10fe4d
Fix whitespace in template
Jrice1317 May 18, 2026
f50394f
Remove redundant logic and improve wording
Jrice1317 May 18, 2026
9ba611f
Fix test
Jrice1317 May 18, 2026
c0b7fd3
Update docs
Jrice1317 May 18, 2026
3fec340
Make tests linux only
Jrice1317 May 18, 2026
15413f2
Remove restriction on docker_tag and apply code review suggestions
Jrice1317 May 20, 2026
1b534dc
Add cross-build support to tests
Jrice1317 May 20, 2026
6998d3d
Update the docs
Jrice1317 May 20, 2026
c0e7735
Fix typo
Jrice1317 May 20, 2026
2a6c4cc
Add output for debugging
Jrice1317 May 20, 2026
0b5ce84
Add docker buildx check to utils
Jrice1317 May 20, 2026
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
26 changes: 25 additions & 1 deletion CONSTRUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ The type of the installer being created. Possible values are:
- `sh`: shell-based installer for Linux or macOS
- `pkg`: macOS GUI installer built with Apple's `pkgbuild`
- `exe`: Windows GUI installer built with NSIS
- `docker`: generates a `Dockerfile` or image of the installation environment.

The default type is `sh` on Linux and macOS, and `exe` on Windows. A special
value of `all` builds _both_ `sh` and `pkg` installers on macOS, as well
Expand Down Expand Up @@ -419,7 +420,7 @@ is `${HOME}/<NAME>` (or, if `HOME` is not set, `/opt/<NAME>`). On Windows,
this is used only for "Just Me" installations; for "All Users" installations,
use the `default_prefix_all_users` key. If not provided, the default prefix
is `%USERPROFILE%\<NAME>`. Environment variables will be expanded at
install time.
install time. If creating a Docker output, the default is `/opt/<NAME>` and can be overridden during the Docker build process.

### `default_prefix_domain_user`

Expand Down Expand Up @@ -679,6 +680,29 @@ freeze_base:
message: "This base environment is frozen and cannot be modified."
```

### `docker_base_image`

Required to use docker-related features.
Base image to use for docker builds and Dockerfiles. This can be any valid docker image reference, including a tag and/or digest.
For example: `debian:13.4-slim@sha256:abc123...`.

### `docker_tag`

Tag to use for the docker image.
If not provided, it will default to `<name>:<version>`.
Has no effect if not using the `docker_image` feature.

### `docker_labels`

Additional labels to add to the built docker image.
The labels `org.opencontainers.image.title` and `org.opencontainers.image.version`
are set automatically from `name` and `version`.

### `docker_image`

If set, builds a docker image using the Dockerfile generated by constructor and saves it as a portable tarball either uncompressed or compressed.
``<name>-<version>-<platform>-<arch>-docker.tar`` will be created in the output docker directory.


## Available selectors
- `aarch64`
Expand Down
27 changes: 26 additions & 1 deletion constructor/_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class InstallerTypes(StrEnum):
EXE = "exe"
PKG = "pkg"
SH = "sh"
DOCKER = "docker"


class PkgDomains(StrEnum):
Expand Down Expand Up @@ -403,6 +404,7 @@ class ConstructorConfiguration(BaseModel):
- `sh`: shell-based installer for Linux or macOS
- `pkg`: macOS GUI installer built with Apple's `pkgbuild`
- `exe`: Windows GUI installer built with NSIS
- `docker`: generates a `Dockerfile` or image of the installation environment.

The default type is `sh` on Linux and macOS, and `exe` on Windows. A special
value of `all` builds _both_ `sh` and `pkg` installers on macOS, as well
Expand Down Expand Up @@ -588,7 +590,7 @@ class ConstructorConfiguration(BaseModel):
this is used only for "Just Me" installations; for "All Users" installations,
use the `default_prefix_all_users` key. If not provided, the default prefix
is `%USERPROFILE%\\<NAME>`. Environment variables will be expanded at
install time.
install time. If creating a Docker output, the default is `/opt/<NAME>` and can be overridden during the Docker build process.
"""
default_prefix_domain_user: NonEmptyStr | None = None
"""
Expand Down Expand Up @@ -853,6 +855,29 @@ class ConstructorConfiguration(BaseModel):
message: "This base environment is frozen and cannot be modified."
```
"""
docker_base_image: Annotated[str, Field(min_length=1)] | None = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of prefixing all fields with docker_*, would it make more sense to make docker its own key with all other properties being a subkey? I know that's not our current convention, so I'm not 100% sure about that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it could be cleaner, but unless we can do a broader refactor, I think docker_* keeps things consistent with how it's handled today.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please volunteer me if/when we can do a schema refactor. (:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sticking with the current schema is a fair choice. A schema refactor would constitute a major breaking change that I don't foresee ourselves doing unless we absolutely have to.

"""
Required to use docker-related features.
Base image to use for docker builds and Dockerfiles. This can be any valid docker image reference, including a tag and/or digest.
For example: `debian:13.4-slim@sha256:abc123...`.
"""
docker_tag: NonEmptyStr | None = None
"""
Tag to use for the docker image.
If not provided, it will default to `<name>:<version>`.
Has no effect if not using the `docker_image` feature.
"""
docker_labels: dict[NonEmptyStr, NonEmptyStr] = {}
"""
Additional labels to add to the built docker image.
The labels `org.opencontainers.image.title` and `org.opencontainers.image.version`
are set automatically from `name` and `version`.
"""
docker_image: Literal["tar", "gz", "zst"] | None = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add explicit NotImplementedError for those values that are not yet implemented? I see in the PR it says:

(gz, zst) are defined in the schema but not yet implemented.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

"""
If set, builds a docker image using the Dockerfile generated by constructor and saves it as a portable tarball either uncompressed or compressed.
``<name>-<version>-<platform>-<arch>-docker.tar`` will be created in the output docker directory.
"""


def fix_descriptions(obj):
Expand Down
66 changes: 63 additions & 3 deletions constructor/data/construct.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@
"all",
"exe",
"pkg",
"sh"
"sh",
"docker"
],
"title": "InstallerTypes",
"type": "string"
Expand Down Expand Up @@ -607,7 +608,7 @@
}
],
"default": null,
"description": "Set default install prefix. On Linux, if not provided, the default prefix is `${HOME}/<NAME>` (or, if `HOME` is not set, `/opt/<NAME>`). On Windows, this is used only for \"Just Me\" installations; for \"All Users\" installations, use the `default_prefix_all_users` key. If not provided, the default prefix is `%USERPROFILE%\\<NAME>`. Environment variables will be expanded at install time.",
"description": "Set default install prefix. On Linux, if not provided, the default prefix is `${HOME}/<NAME>` (or, if `HOME` is not set, `/opt/<NAME>`). On Windows, this is used only for \"Just Me\" installations; for \"All Users\" installations, use the `default_prefix_all_users` key. If not provided, the default prefix is `%USERPROFILE%\\<NAME>`. Environment variables will be expanded at install time. If creating a Docker output, the default is `/opt/<NAME>` and can be overridden during the Docker build process.",
"title": "Default Prefix"
},
"default_prefix_all_users": {
Expand Down Expand Up @@ -638,6 +639,65 @@
"description": "Set default installation prefix for domain users. If not provided, the installation prefix for domain users will be `%LOCALAPPDATA%\\<NAME>`. By default, it is different from the `default_prefix` value to avoid installing the distribution into the roaming profile. Environment variables will be expanded at install time. Windows only.",
"title": "Default Prefix Domain User"
},
"docker_base_image": {
"anyOf": [
{
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Required to use docker-related features. Base image to use for docker builds and Dockerfiles. This can be any valid docker image reference, including a tag and/or digest. For example: `debian:13.4-slim@sha256:abc123...`.",
"title": "Docker Base Image"
},
"docker_image": {
"anyOf": [
{
"enum": [
"tar",
"gz",
"zst"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "If set, builds a docker image using the Dockerfile generated by constructor and saves it as a portable tarball either uncompressed or compressed. ``<name>-<version>-<platform>-<arch>-docker.tar`` will be created in the output docker directory.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the format <name>-<version>-<platform>-<arch> true?
From what I could see in the code it looks like:

tag = info.get("docker_tag", f"{info['name'].lower()}:{info['version']}")

"title": "Docker Image"
},
"docker_labels": {
"additionalProperties": {
"minLength": 1,
"type": "string"
},
"default": {},
"description": "Additional labels to add to the built docker image. The labels `org.opencontainers.image.title` and `org.opencontainers.image.version` are set automatically from `name` and `version`.",
"propertyNames": {
"minLength": 1
},
"title": "Docker Labels",
"type": "object"
},
"docker_tag": {
"anyOf": [
{
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Tag to use for the docker image. If not provided, it will default to `<name>:<version>`. Has no effect if not using the `docker_image` feature.",
"title": "Docker Tag"
},
"environment": {
"anyOf": [
{
Expand Down Expand Up @@ -864,7 +924,7 @@
}
],
"default": null,
"description": "The type of the installer being created. Possible values are:\n- `sh`: shell-based installer for Linux or macOS\n- `pkg`: macOS GUI installer built with Apple's `pkgbuild`\n- `exe`: Windows GUI installer built with NSIS\nThe default type is `sh` on Linux and macOS, and `exe` on Windows. A special value of `all` builds _both_ `sh` and `pkg` installers on macOS, as well as `sh` on Linux and `exe` on Windows.",
"description": "The type of the installer being created. Possible values are:\n- `sh`: shell-based installer for Linux or macOS\n- `pkg`: macOS GUI installer built with Apple's `pkgbuild`\n- `exe`: Windows GUI installer built with NSIS\n- `docker`: generates a `Dockerfile` or image of the installation environment.\nThe default type is `sh` on Linux and macOS, and `exe` on Windows. A special value of `all` builds _both_ `sh` and `pkg` installers on macOS, as well as `sh` on Linux and `exe` on Windows.",
"title": "Installer Type"
},
"keep_pkgs": {
Expand Down
166 changes: 166 additions & 0 deletions constructor/docker_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""Logic for creating a Dockerfile and/or building portable Docker images from Constructor installers."""

import logging
import shutil
import subprocess
import tempfile
from pathlib import Path

from jinja2 import Template

from . import __version__

logger = logging.getLogger(__name__)

TEMPLATE_PATH = Path(__file__).parent / "dockerfile_template.tmpl"

DOCKER_PLATFORM_MAP = {
"linux-64": "linux/amd64",
"linux-aarch64": "linux/arm64",
"linux-armv7l": "linux/arm/v7",
"linux-32": "linux/386",
"linux-ppc64le": "linux/ppc64le",
"linux-s390x": "linux/s390x",
}


def generate_dockerfile(info: dict, docker_dir: Path) -> Path:
"""
Render the Dockerfile template and write it to the Docker build directory.

Parameters
----------
info: dict
Constructor installer info dict.
docker_dir: Path
Path to the Docker build directory returned by prepare_docker_context().

Returns
-------
Path
Path to the generated Dockerfile.
"""
from .conda_interface import MatchSpec

specs = {MatchSpec(spec).name for spec in info.get("specs", ())}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
specs = {MatchSpec(spec).name for spec in info.get("specs", ())}
specs = {MatchSpec(spec).name for spec in info.get("specs", ())}
has_mamba = "mamba" in specs

That will make things more clear later. Should probably also be done with conda because not every installation has conda or mamba.

has_mamba = "mamba" in specs

docker_template = Template(TEMPLATE_PATH.read_text())

rendered_dockerfile = docker_template.render(
constructor_version=__version__,
base_image=info.get("docker_base_image"),
default_prefix=info.get("default_prefix", f"/opt/{info['name'].lower()}"),
Comment thread
Jrice1317 marked this conversation as resolved.
installer_filename=Path(info["_outpath"]).name,
name=info["name"],
version=info["version"],
labels=info.get("docker_labels", {}),
init_cmd="$PREFIX/bin/mamba shell" if has_mamba else "$PREFIX/bin/python -m conda",
register_envs=info.get("register_envs"),
keep_pkgs=info.get("keep_pkgs"),
)

logger.info("Writing Dockerfile...")
dockerfile_path = docker_dir / "Dockerfile"
dockerfile_path.write_text(rendered_dockerfile)
return dockerfile_path


def build_image(info: dict, docker_dir: Path) -> Path:
"""Optionally build the docker image from the generated Dockerfile.
Currently supported on linux and macOS platforms.

Parameters
----------
info: dict
Constructor installer info dict.
docker_dir: Path
Path to the Docker directory containing the Docker outputs.

Returns
-------
Path
Path to the saved Docker image tarball.

"""
if not (docker_platform := DOCKER_PLATFORM_MAP.get(info["_platform"])):
raise RuntimeError(
f"Unsupported platform for Docker build: {info['_platform']}. "
f"Supported platforms are: {', '.join(DOCKER_PLATFORM_MAP.keys())}."
)

tag = info.get("docker_tag", f"{info['name'].lower()}:{info['version']}")
tarball_dest = docker_dir / f"{Path(info['_outpath']).stem}-docker.tar"

cmd = [
"docker",
"buildx",
"build",
str(docker_dir),
"--platform",
docker_platform,
"-t",
tag,
Comment on lines +102 to +103
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is where I'm wondering if we need the tag - can we also retrieve the Docker image based on the emitted SHA?

Copy link
Copy Markdown
Contributor Author

@Jrice1317 Jrice1317 May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry I'm not understanding the ask here. If we do not provide a tag, the image would show up like <none> <none>.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you thinking we use the SHA to pass into docker save instead of the tag?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tag right now is mandatory docker build input in this implementation. However, locally, docker build can be run without a tag. So, is a tag required for this to work? Could we also create a portable image without creating a tag? The SHA is one option - I don't know docker build well enough to confidently answer that question, I'm afraid.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is not required to work, but we most definitely should use a tag. When someone tries to run the image, one of the easiest ways to do that is to use the tag. If we don't provide a tag, there would be a <none> <none> and that doesn't look good or safe. So, I decided to default to the <name>:<version> but the user could choose their own tag by providing it via the docker_tag.

Copy link
Copy Markdown
Contributor Author

@Jrice1317 Jrice1317 May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if you build an image with no tag and you look at your list of images using the docker images command, you will see something like:

REPOSITORY                        TAG                                        IMAGE ID       CREATED        SIZE
<none>                            <none>                                     ff7aa1ac149b    1 min ago    417MB

Docker tags are not the same as GitHub tags. It is the name of the image and we should definitely have one. If no tag is provided, we would require using --iidfile, but tag is more predictable and there's no security concern in this context.

Copy link
Copy Markdown
Contributor Author

@Jrice1317 Jrice1317 May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify intent: the local image is intentional — it's available for immediate use and testing after the build, while the tarball is for portability and sharing. I'd rather not cleanup by default since that would silently remove something the user may want, but I'm happy to add it if that's the preference. Either way, I just realized I need to update the docstring to clarify the artifacts and mention that only tar is supported at the moment. We could also add the cleanup and additional compression options in a different PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is what I see using version 29.4.3 on macOS:

IMAGE                              ID             DISK USAGE   CONTENT SIZE   EXTRA
<untagged>                         1f94b86ca4f7       1.98GB          467MB

So, the output seems to be different for different versions and maybe platforms.

If no tag is provided, we would require using --iidfile,

It's a temporary file, so that's not an issue unless there are reliability concerns.

but tag is more predictable and there's no security concern in this context.

I'm still not sure what the security concern is here. What is the exploit a tag prevents that an image ID or a SHA is vulnerable to? Tags are generally more vulnerable than a hash because tags are mutable (including Docker tags) whereas hashes are not - which is why the general recommendation is to add the hash to the base image.

It is the name of the image and we should definitely have one.

docker run can take a tag, ID, or hash of the image. I agree that for publication on DockerHub, a tag is very useful. But "should" is very different from "must" - the current code makes tags an absolute requirement rather than a recommendation.

So, if you want to impose that restriction, that's a design choice. I do not see the security angle here though, but it appears to be based on a specific (even if common) workflow. But then we need to be defensive and error out if a user writes docker_tag: "" into the YAML file. Or we allow it by documenting that a user can disable a tag by making it an empty string.

To clarify intent: the local image is intentional — it's available for immediate use and testing after the build, while the tarball is for portability and sharing. I'd rather not cleanup by default since that would silently remove something the user may want,

If a user wants the image immediately afterwards, they can use docker load to do so. I think making a user clean up afterwards is worse UX than making a user explicitly use the image.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing untagged images adds complexity with no real benefit in this context. It would require --iidfile, changes to the docker save step, and additional test coverage for an edge case that makes the UX worse, not better. I think requiring a tag is a reasonable design choice here.

To your point about docker_tag: "", that will error out on its own since the schema enforces NonEmptyStr, so no additional defensive check is needed.

I also want to clarify my use of "tag" throughout this thread. When I say tag, I mean the full -t name:tag argument, since that's how Docker handles both the repository name and the version label together. An untagged image has neither, which is what produces the <none> <none> or <untagged> output depending on your Docker version and platform, as you showed.

I've removed the restriction requiring : to be present in docker_tag. If the user provides only a name with no :, Docker will default the tag to latest, which is standard behavior.

And on cleanup, I've added docker rmi after docker save as suggested. I agree that's better UX than leaving it to the user.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference also all of my images are tagged:

PS C:\Users\Robin> docker images
REPOSITORY               TAG       IMAGE ID       CREATED        SIZE
test-t-flag              latest    b9921cefdfe9   6 weeks ago    2.72GB
menuinst-linux-test      latest    31b21124bbe2   7 weeks ago    3.82GB
continuumio/miniconda3   26.1.1    b002473017c7   2 months ago   1.02GB
ubuntu                   24.04     d1e2e92c075e   3 months ago   117MB
ubuntu                   latest    66460d557b25   7 months ago   117MB

Note here that REPOSITORY maps to name and if you build multiple images without specifying name/tag it's difficult to know which image is which (if they have none for both REPOSITORY and TAG).

"--load",
]

logger.info("Building Docker image: '%s'", tag)
try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
# Gather diagnostics on failure
docker_version = subprocess.run(["docker", "--version"], capture_output=True, text=True)
buildx_version = subprocess.run(
["docker", "buildx", "version"], capture_output=True, text=True
)
buildx_ls = subprocess.run(["docker", "buildx", "ls"], capture_output=True, text=True)
raise RuntimeError(
f"Docker build failed.\n"
f"Command: {cmd}\n"
f"Docker version: {docker_version.stdout.strip()}\n"
f"Buildx version: {buildx_version.stdout.strip() or buildx_version.stderr.strip()}\n"
f"Buildx builders: {buildx_ls.stdout.strip()}"
) from e

logger.info("Saving Docker image to tarball: '%s'", tarball_dest)
subprocess.run(["docker", "save", tag, "-o", str(tarball_dest)], check=True)
subprocess.run(["docker", "rmi", tag], check=False)
return tarball_dest


def create(info: dict, verbose: bool = False) -> None:
"""Build a Docker output

Parameters
----------
info: dict
Constructor installer info dict.
verbose: bool, optional
If ``True``, enables verbose logging.
Defaults to ``False``.

"""
with tempfile.TemporaryDirectory() as temp_dir:
docker_tmp_dir = Path(temp_dir)

installer_path = Path(info["_outpath"])
if not installer_path.exists():
raise RuntimeError(f"Expected .sh installer not found: {installer_path}")
shutil.copy(installer_path, docker_tmp_dir / installer_path.name)
logger.info("Copied installer to build directory.")

generate_dockerfile(info, docker_tmp_dir)

if info.get("docker_image") == "tar":
tarball = build_image(info, docker_tmp_dir)
shutil.copy(tarball, Path(info["_output_dir"]) / tarball.name)
else:
output_dir = Path(info["_output_dir"]) / installer_path.stem
output_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(docker_tmp_dir / "Dockerfile", output_dir / "Dockerfile")
shutil.copy(
docker_tmp_dir / Path(info["_outpath"]).name,
output_dir / Path(info["_outpath"]).name,
)

logger.info("Docker output complete. Docker directory: '%s'", info["_output_dir"])
Loading
Loading