Skip to content

[FEATURE] GHCR registry should use 3.2 images with LEAP, not Alpine base #2422

Open

Description

@polarathene

EDIT: Still the case in April 2025.


Related:

Expected behavior

GHCR and DockerHub registries should be publishing the same image builds.

Actual behavior

# Different images:
docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive docker.io/drwetter/testssl.sh:3.2
docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive ghcr.io/testssl/testssl.sh:3.2

Your DockerHub and GHCR publishing seems to be handled differently? You have a Github Actions workflow for the GHCR registry, but it's targeting the Dockerfile.git (Old Alpine) image in the 3.2 branch:

file: Dockerfile.git

tags: |
ghcr.io/${{ github.repository }}:${{ env.BUILD_VERSION }}
ghcr.io/${{ github.repository }}:latest

FROM alpine:3.17

DockerHub is apparently watching the repo and builds automatically (thus the Dockerfile used from each branch associated to a tag?): #2085 (comment)


This file was only added AFAIK for CI usage, where it builds differently by performing a git clone of the repo instead of using COPY:

&& git clone --depth 1 --branch ${BUILD_VERSION} $URL /home/testssl \

Allowing via build ARG to control the repo and branch (ARCHIVE_URL doesn't appear to be used):

ARG BUILD_VERSION
ARG ARCHIVE_URL=https://github.com/drwetter/testssl.sh/archive/
ARG URL=https://github.com/drwetter/testssl.sh.git

The CI itself is only using the build version:

build-args: BUILD_VERSION


There is rarely a need for this style of image building?

  • Usually any fork of testssl.sh is going to also have the Dockerfile associated to it.
  • This would just be building with the source a remote of a repo + branch where you don't want to use the Dockerfile in sync with that commit?
  • What advantage / need does it serve over a local git clone and building from the Dockerfile in that repo?

IIRC, it was possibly used in the past to have the CI support building the different branches of this project. However the CI should be triggered and building with the correct branch source here?:

- name: Source checkout
uses: actions/checkout@v4

EDIT: You've been reluctant to remove it because of the possibility of someone else building from it. There is no other known consumer beyond the CI, I'd encourage dropping it until you have a valid reason to keep it (revert the removal and address the image changes required at that point).

I am aware of one user @jauderho that would need to update their workflow, should the file be dropped. Perhaps they can weigh in on if that'd be an issue for them.


Likewise you have for both 3.0 and 3.2 these branch triggers, both with scheduled cron jobs:

on:
push:
branches:
- 3.2
workflow_dispatch:
schedule:
- cron: "0 8 * * 1"

on:
push:
branches:
- 3.0
workflow_dispatch:
schedule:
- cron: "0 8 * * 1"

However scheduled workflows are only from the default branch (now 3.2): https://github.com/testssl/testssl.sh/actions/workflows/docker-3.2.yml

As you'll see no scheduled builds are in the 3.0 history for the past year: https://github.com/testssl/testssl.sh/actions/workflows/docker-3.0.yml

You could adapt the scheduled workflow of docker-mailserver, which references a workflow to run and the associated branch (@).

That lives on the default branch, and without providing a branch reference, will run the referenced workflow from that scheduled run workflows last commit (not what you want when referencing workflows to run that get updated), thus referencing the branch is important.

Activity

  1. changed the title [-][BUG] GHCR registry is publishing `3.2` images with Alpine base[/-] [+][FEATURE] GHCR registry should use `3.2` images with LEAP, not Alpine base[/+] on Oct 13, 2023
  2. drwetter commented on Oct 13, 2023

    @drwetter
    Collaborator

    IIRC I asked you when you refactored the Dockerfile whether you could do the same for the GCHR Dockerfile and your responded that you lacked time.

    Don't know who's actually using the GCHR file and I can't recall atm the circumstances why it is like it is. Maybe @jauderho was involved.

    If you believe it should be changed, you're welcome to make a proposal. It would be great to keep it short and concise.

  3. polarathene commented on Oct 15, 2023

    @polarathene
    ContributorAuthor

    It would be great to keep it short and concise.

    I am open to contributing a PR to revise your Github Actions with the docker image workflows. It may not be "short and concise", but well intentioned and beneficial for maintenance. The technical reasoning is below.

    I propose:

    • A separate scheduled workflow file in your default branch (currently 3.2) which is required for this to actually work for scheduled builds of 3.0 (or any other branch that isn't the default). This workflow would have separate jobs defined to build the different versions (release branches) you want to support (similar to this example, although it's jobs are for a different purpose).
    • As noted by the example reference, the jobs will reference a reusable workflow.
      • Those likewise use the default branch, but should explicitly reference it with @ to work the way you'd expect.
      • You could have each job reference the specific branch instead, and maintain variants of the reusable workflow called, but I don't think you'd need this and it'd be better to keep it consistent to the same reference branch (you could keep that in sync with your default branch, or have a separate dedicated branch for this). The only real change needed is to provide an input (the release branch), this will be easier to maintain and reason about.
    • The reusable workflow will handle the build + publish process. It will be similar to what you have already, but I'd like to revise it a little for your benefit.
      • Unfortunately your tags don't use proper semver, and your current process is to override the 3.0 / 3.2 tag without support for specific releases (which could be supported).
      • Instead, the tags will match your current workflow by using the branch name as the tag.
      • I can additionally include a default :latest tag that points to your current default branch (3.2 presently, but you tend to adjust this as the version changes rather than an edge or similar unreleased branch (typically main/master or develop)). It will adapt to your default branch changes implicitly, meeting the expectations of your users when no tag is specified?
    • I would suggest having both GHCR and DockerHub published this way for consistency. Disabling your existing DockerHub integration in favour of this workflow, centralizes your management of it. This has been discussed in the past, but you seemed a bit reluctant towards it.
    • While I can provide you with a single reusable workflow, I am fond of splitting the responsibility of build and publishing to separate workflow files.
      • For example, should you decide to run tests via the container in CI, you can easily reuse the build workflow this way without any publishing concerns involved until tests pass.
      • The reusable workflow will already be leveraged between your branches being updated and the scheduled jobs, where both scenarios are building and publishing the image (at least for GHCR, your DockerHub builds won't benefit unless you're willing to publish from Github Actions).

    The above would drop the Dockerfile.git as I don't think it serves any real value for you to have and maintain. Users should just clone the repo + branch of interest and build the Dockerfile.

  4. drwetter commented on Apr 25, 2025

    @drwetter
    Collaborator
  5. drwetter commented on Apr 25, 2025

    @drwetter
    Collaborator

    Looks like GHCR pulls from docker hub and the images opensuse is providing for 15.6 are here: https://hub.docker.com/layers/opensuse/leap/15.6/images/sha256-a5b038ad32138fe8600b978422290bae846b24f4fdbf6026eb0a4c89efa6f161 . So there is linux/amd64, linux/arm64, linux/ppc64le, linux/s390x but no linux/386, linux/arm/v7, linux/arm/v6

    I have no clue about the usage of the usage of 386, arm/v7, arm/v6.

  6. polarathene commented on Apr 25, 2025

    @polarathene
    ContributorAuthor

    Looks like GHCR pulls from docker hub and the images opensuse is providing for 15.6

    Yes the base image without a registry prefix will fallback to docker.io (DockerHub) when using Docker Buildx. I don't think openSUSE has images on GHCR, but they do have their own registry that could probably be used if you preferred that over DockerHub.

    Shouldn't be an issue to leave as-is AFAIK.


    I have no clue about the usage of the usage of 386, arm/v7, arm/v6.

    The ARM v7 and v6 archs are legacy. I'd say the same about 386 (roughly x86 32-bit). I doubt that the ppc64le + s390x targets get used that much too?

    May be better to just publish the standard amd64 + arm64 archs, and then wait upon users to open issues if there are any expecting images for the other platforms?

  7. drwetter commented on Apr 26, 2025

    @drwetter
    Collaborator

    🤷‍♂️ can't tell. I kind of like ancient hardware and people using it.

    Alpine had issues (left is probably DNS, see other thread(s).

    So either 1) somebody retests the edge cases in Alpine and we'll get a "go", then we could switch back to Alpine. My guts say we will run into problems 😃 and maybe it's not worth spending time for that.

    1. would be picking Fedora instead -- you offered that a while back before . The only thing which concerns me that it needs care every 6-1 months. For opensuse leap I needed to make some changes as some binaries were removed(?) and others were in other packages.

    2. Debian is an option because it's always an option :-) and they support almost every architecture. Image is larger though. Have you tried a multitstage image before, I can't remember?

    This is just the Debian image, there's no socat or openssl in there:

    prompt:~|% docker image ls                                                                                                                                  
    REPOSITORY                    TAG             IMAGE ID       CREATED        SIZE
    debian                        latest          1fd9a3236e02   2 weeks ago    117MB
    drwetter/testssl.sh           latest          b731f3b2622c   3 weeks ago    54.2MB
    [..]
    ghcr.io/testssl/testssl.sh    3.0             2fe83bf0e85a   6 weeks ago    59.7MB
    
    1. And Ubuntu, well it's similar to Debian. I'd rather go for Debian then.

    Chisel is an option but if you say it's not that stable yet, I'd rather stay with a classical or multistage image. And: see also your comment EDIT: Actual image size reduction wasn't as big as I initially thought

  8. jauderho commented on Apr 26, 2025

    @jauderho
    Contributor
    • The DNS issues should have been resolved as of Alpine 3.18 (released over a year ago). The current release is 3.21.x. See [1]
    • Ubuntu tends to have newer versions of software packages compared to Debian
    • If you look at official Docker support, IIRC Ubuntu has the widest range of supported platforms [2]
    • It is not too hard for me to spin up Ubuntu based builds so I can tackle that at some point if people are interested in testing that out
    • I find that Docker Hub appears to be pretty aggressive at throttling pulls so I have been moving towards asking people to prioritize pulling from GHCR instead

    [1] https://www.theregister.com/2023/05/16/alpine_linux_318/
    [2] https://docs.docker.com/engine/install/

  9. drwetter commented on Apr 27, 2025

    @drwetter
    Collaborator
    • The DNS issues should have been resolved as of Alpine 3.18 (released over a year ago). The current release is 3.21.x. See [1]

    Off the top of my head I can't recall what the problems were (here) with docker and DNS. We had without docker DNS issues especially with NSS and I am glad that they are solved. Thus I am not tempted to roll that up again, unless one does extensive testing to convince me that all problems are gone.

    Debian vs. Ubuntu: The number of architectures are pretty similar (https://github.com/docker-library/official-images#architectures-other-than-amd64). Also I doubt that this project needs always the latest and greatest binaries with one exception though: OpenSSL 3.5.0 would be great to have probably -- that's included in Debian Trixie, to be released sometime in June.

    Both base images are bigger though, if @polarathene can squeeze the image with a multistage build, that probably would help.

    find that Docker Hub appears to be pretty aggressive at throttling pulls so I have been moving towards asking people to prioritize pulling from GHCR instead

    Ok, personally I don't care much where people get the docker image from. I'd like to offer both though. Just for my information: Do you have numbers or did others also report that? As of now the account is the official image for this project is a payed one and if it's being throttled , that would be not fine.

  10. jauderho commented on Apr 28, 2025

    @jauderho
    Contributor

    I threw together a quick Dockerfile for Ubuntu yesterday. https://github.com/jauderho/dockerfiles/blob/main/testssl.sh/Dockerfile.ubuntu

    Build command:
    DOCKER_BUILDKIT=0 docker build -t testssl --build-arg "BUILD_VERSION=v3.2.0" -f Dockerfile.ubuntu .

    Image size

    REPOSITORY                        TAG       IMAGE ID       CREATED          SIZE
    testssl                           latest    5cf8e9ff6bde   12 minutes ago   172MB
    

    I'm open to suggestions on how to pare down the image from @polarathene or anyone else.

  11. polarathene commented on Apr 28, 2025

    @polarathene
    ContributorAuthor

    Verbose response incoming 😆


    Platforms

    🤷‍♂️ can't tell. I kind of like ancient hardware and people using it.

    My policy is not to release to archs that nobody has specifically requested to be supported. As you said you have no stats, for all you know it's a waste of resources (which we can shrug at since it's at no cost to us, but it's still a waste).

    It's rather simple to drop platforms and then have a user chime in with "hey what happened to X? I need that" and you restore it 🤷‍♂ (it's editing one line for you AFAIK)

    The appeal of someone using such maybe is neat, but if there were support issues that you can't reproduce and are likely due to their niche platform usage, is the platform actually going to receive support or is the time not justified to resolve it?

    For projects I maintain, users must express interest by requesting the platform and there needs to be enough demand to warrant it vs them making a local build. ARMv7 was dropped with any remaining users directed to build the image themselves or establish a community image for that arch (didn't happen AFAIK), or spend <$50 to get a newer system (Last popular ARMv7 device I recall was I think was 2016 or older).


    Fedora

    would be picking Fedora instead -- you offered that a while back before . The only thing which concerns me that it needs care every 6-1 months

    You know you can upgrade the base image at whatever pace is comfortable for you right?

    If you're already comfortable with other distros for the base image due to a slower release cadence and have no concerns about the older versions packaged... what difference would it be to use a release of Fedora for the same time frame?

    Fedora EOL tends to be approx a year after release, so no more security / bugfix updates after that. As a maintainer for a Debian based image, the major upgrades are roughly 2 years apart, with a lot more changes to accommodate at once, this is less of a concern when the distro releases in a shorter cycle as it's spread out.

    FWIW, you didn't have much concerns with Alpine did you and that released as often as Fedora, but has a 2 year EOL window instead.


    Debian / Ubuntu

    Have you tried a multitstage image before, I can't remember?

    If you're referring to Debian with a similar size optimization to Leap and Fedora, the package manager doesn't offer that. I believe there's something called deboostrap, but I've not investigated it to compare.

    • I don't think it was as simple to leverage otherwise I'd probably have given it a whirl for docker-mailserver.
    • I believe I looked into it when trying with Fedora and Leap images to switch from Alpine and it was too large.

    Both base images are bigger though, if @polarathene can squeeze the image with a multistage build, that probably would help.

    I can't really assist with Ubuntu/Debian, other than via chisel which is bound to Ubuntu release cycles (April/October) and has reduced package selection.

    chisel is great for static binary builds that only need core files / packages to run, but something like this project which is a shell script relying on various packages for commands to call may take longer to land slices ("package" support).


    Chisel

    And Ubuntu, well it's similar to Debian. I'd rather go for Debian then.

    Chisel is an option but if you say it's not that stable yet, I'd rather stay with a classical or multistage image. And: see also your comment EDIT: Actual image size reduction wasn't as big as I initially thought

    For reference (for anyone else curious about the prior chisel discussion).

    Chisel is stable AFAIK, it's used by some official images like dotNET language and various Canonical images. I said it was fairly new (only hit 1.0.0 a few months ago IIRC), that the UX could be improved given some more time to mature (I've opened various issues at their repo that you can filter for regarding that).

    For you to switch to chisel, you are presently blocked until this socat slice is added, or you maintain that yourself in the meantime. I'm not too familiar with their slice config (it's effectively config of what to retain from a package, stripping out any weight not deemed important), but the example I reference at that issue can probably be opened as a PR as-is and adjusted with reviewer feedback, since I did find that slice from a past PR that later didn't need it.


    Chisel examples

    Here's what you can do with chisel atm (but lacking socat functionality):

    Dockerfile example
    # syntax=docker/dockerfile:1.10
    
    # NOTE: This will be redundant once an official chisel image is released:
    FROM alpine AS chisel
    ARG CHISEL_VERSION=1.1.0
    ARG TARGETARCH
    ARG CHISEL_RELEASE="https://github.com/canonical/chisel/releases/download/v${CHISEL_VERSION}/chisel_v${CHISEL_VERSION}_linux_${TARGETARCH}.tar.gz"
    RUN wget -qO - "${CHISEL_RELEASE}" | tar -xz --no-same-owner -C /usr/local/bin chisel
    
    # Prepare root-fs with required packages:
    FROM chisel AS root-fs
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        chisel cut --release ubuntu-24.04 --root /root-fs \
            base-files_base bash_bins procps_bins grep_bins gawk_bins sed_bins coreutils_bins busybox_bins openssl_bins curl_bins passwd_bins dash_bins
    
    # In a future chisel release there should be no need to COPY /root-fs,
    # it should all work in a single stage (but may not be optimal weight without complexity)
    FROM scratch
    COPY --link --from=root-fs /root-fs /
    RUN <<HEREDOC
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl:
        useradd --uid 1000 --user-group --create-home --shell /bin/bash testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        mkdir -p /usr/local/bin
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    
    # Copy over build context (after filtered by .dockerignore): bin/ etc/ testssl.sh
    COPY --chown=testssl:testssl . /home/testssl/
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]
    • mkdir -p /usr/local/bin was necessary to add as chisel doesn't provide that by default like openSUSE, Ubuntu and other actual distros do.

    • dash_bins provides dash shell, it was only added for the /usr/bin/sh symlink, you could manually add that but for a RUN to work like above, you'd need to switch to the bash shell as default (for RUN) via SHELL ["/bin/bash", "-c"]. Only 100KB difference in this case, so might as well keep it.

    • passwd_bins provides passwd package for useradd. You could use the current Dockerfile approach I added for a lighter weight user creation (4MB lighter):

      echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
      echo 'testssl:x:1000:' >> /etc/group
      echo 'testssl:!::0:::::' >> /etc/shadow
      install --mode 2755 --owner testssl --group testssl --directory /home/testssl

      Looking at what chisel with passwd-base_data slice would install, and Google's distroless (Debian), neither actually has an /etc/shadow so you could skip that. Personally I'd probably skip the non-root user too (could install testssl files to /opt/testssl), but up to you.

    Once there is a socat slice and official chisel image published, you could slim it down to just this (also assumes chisel embeds a CA bundle, otherwise COPY one prior to RUN):

    Dockerfile example
    # syntax=docker/dockerfile:1.10
    
    FROM scratch
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        --mount=type=bind,from=chisel,source=/usr/local/bin/chisel,target=/usr/local/bin/chisel \
        ["chisel", "cut", "--release", "ubuntu-24.04", "--root", "/", "base-files_base", "dash_bins"]
    
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        --mount=type=bind,from=chisel,source=/usr/local/bin/chisel,target=/usr/local/bin/chisel \
        <<HEREDOC
        chisel cut --release ubuntu-24.04 --root / \
            bash_bins procps_bins grep_bins gawk_bins sed_bins coreutils_bins busybox_bins openssl_bins curl_bins socat_bins
    
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl:
        echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
        echo 'testssl:x:1000:' >> /etc/group
        install --mode 2755 --owner testssl --group testssl --directory /home/testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    
    COPY --chown=testssl:testssl . /home/testssl/
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]
    • The usage of an earlier RUN presently is not space efficient with chisel (but is planned to be).

    • This will produce the same image size as the prior example with COPY from /root-fs of an earlier stage.

    • Dropped mkdir -p /usr/local/bin as it is no longer needed due to that path being created via RUN --mount for chisel.

    • The first RUN has no shell on FROM scratch, so uses the JSON RUN syntax (a tad awkward vs COPY from an earlier stage at /root-fs). It provides /usr/bin/sh -> /usr/bin/dash for convenience with the next RUN.

      You could alternatively temporarily mount Nushell (a shell self-contained into a single static linked binary) and use that to run the command.

      Without setting SHELL it'd look like this (treats the chisel command as a single string):

      RUN --mount=type=bind,from=ghcr.io/nushell/nushell,source=/usr/bin/nu,target=/nu \
      ["/nu", "-c", "chisel cut --release ubuntu-24.04 --root / base-files_base dash_bins"]

      Or with SHELL (NOTE: The syntax is not like bash/sh once you go multi-line with HEREDOC):

      SHELL ["/nu", "-c"]
      RUN chisel cut --release ubuntu-24.04 --root / base-files_base dash_bins

      Didn't seem worth it.

    Seems more competitive/simpler than Fedora or Leap variants I think?


    DockerHub throttling

    Just for my information: Do you have numbers or did others also report that? As of now the account is the official image for this project is a payed one and if it's being throttled , that would be not fine.

    I've only heard about the recent change with DockerHub to throttle unpaid users.

    You would not be throttled via a paid account, but anyone pulling your images from the registry that isn't paid will be throttled.

    I have heard that since the change was made that they've relaxed it a bit. I've done a fair amount of image pulling today (free user), and not hit any rate limit issues AFAIK. It may only affect users that are deploying/updating a system with many images at once.


    Alpine / musl quirks

    The DNS issues should have been resolved as of Alpine 3.18 (released over a year ago).

    That's good, but I'd still feel uncomfortable adopting Alpine personally 😓 If it works for you and what you do with it, awesome. I've lost too much time to Alpine / musl troubleshooting with the only motivator being slightly smaller usage on disk/network I/O.

    • Someone claims that even with Alpine 3.18 they had a DNS issue that didn't occur with glibc. However I could not reproduce their success scenario, seems to just be bug with CoreDNS 🤷‍♂
    • There was another issue I tried to reproduce regarding common failures attributed to musl, but I could not reproduce that either despite how much I tried 🤔 (I wanted to at least reproduce the DNS-over-TCP failure prior to Alpine 3.18, but kept failing when trying curl/wget/nslookup/dig/etc, but I was unsuccessful)
    • Alpine still seems to perform DNS queries in parallel unlike glibc (sequential). That's another known issue when there is more than one DNS server to query, which this user notes is problematic if the queried server responds back with NXDOMAIN (eg public vs private DNS, where order queried may be important).
      • Related to that was some other DNS config gets ignored with musl AFAIK which can result in similar woes.
      • There is also this mention of A + AAAA queries being sent out with musl when not necessary, with glibc this could be controlled via /etc/gai.conf. The other issue related to k8s discussed has been resolved IIRC, when there is a large number of hosts to resolve.

    I haven't gone over this or this, some of that may be outdated but the 2nd link shows a fair amount of differences (not DNS specific).

    It's great that Alpine / musl has been resolving common pain points with using it, but I've been bitten enough by it that I'm wary to use it for anything serious (besides DNS, I've experienced performance degradation and also some software detecting "linux" and pulling pre-built binaries that dynamically link to glibc). I just prefer less headaches sticking with glibc personally over potentially saving a few MB 🤷‍♂

  12. polarathene commented on Apr 28, 2025

    @polarathene
    ContributorAuthor

    I'm open to suggestions on how to pare down the image from @polarathene or anyone else.

    You can look into debootstrap like I briefly mentioned above, but I don't know how worthwhile it is.

    There's some tooling like Slim or BLAFS if the project has full test coverage for functionality (I think these tools mostly pay attention to file-level access), and they'll strip out everything else which if done well would still be functional. I've not used either personally.

    The chisel examples in my previous comment are competitive (but without socat, incomplete):

    $ docker image ls | grep testssl
    
    REPOSITORY                      TAG       IMAGE ID       CREATED          SIZE
    localhost/testssl               latest    9f01cc837e21   2 hours ago      51.6MB
    docker.io/drwetter/testssl.sh   3.2       e2c28bc47841   2 days ago       54.3MB
    ghcr.io/testssl/testssl.sh      3.2       2dd7de14f985   2 months ago     60.6MB

    Current official images published:
    docker.io => openSUSE Leap
    ghcr.io => Alpine (Docker.git)

    So chisel (with eventual socat) should be roughly the size of openSUSE Leap, maybe a smidge smaller.

  13. drwetter commented on Apr 29, 2025

    @drwetter
    Collaborator

    @jauderho : 170MB is too large .

    If @polarathene shares my concerns wrt alpine we should not use it. For the platforms: lets agree ro disagree. Problem solved 😃

    Wrt the type of the remaining distros... let's stick to the facts:

    • it should have all binaries we need
    • it should be small, best as current
    • maintenance costs should be close to zero
    • preferably it should include openssl 3.5.0
    • preferably old archs
    • best would be to have a single Dockerfile

    The thing with socat/chisel, @polarathene : I am not sure I understand. Whatever a slice is: can't we just add (binary or package) it somehow? If the answer is no via apt it in another stage of the Dockerfile and later copy the binary? A short answer is also fine . 😃

  14. polarathene commented on Apr 30, 2025

    @polarathene
    ContributorAuthor

    A short answer is also fine

    What you request is possible in a few ways yes.

    Skip to my next comment, it provides a single Dockerfile that meets your wishlist, both socat + OpenSSL 3.5.0 presently need workarounds, but as those are resolved upstream you can drop them, or delay switching to chisel until then.


    Verbose response with full insights follows.

    Whatever a slice is

    chisel is a tool that has a YAML "slice" config per package supported. It basically says what content to keep in a package and discard the rest. These slice configs are per Ubuntu release, presently 25.04 is not supported so there is a bit of a delay there too (reference: chisel release process FAQ).


    Custom slices workaround solution

    Without an official socat slice, you can add your own (like the one shown here), but chisel lacks an overlay-like feature so you would need to git clone a chisel-releases branch (eg Ubuntu 24.04), then add your custom slice and point chisel to use that git repo instead (usually you would just use --release ubuntu-24.04 and chisel would clone that branch itself behind scenes).

    # 552 KB:
    FROM scratch AS patched-release
    ADD https://github.com/canonical/chisel-releases.git#ubuntu-24.04 /chisel-releases
    COPY socat.yaml /chisel-releases/slices/socat.yaml
    
    FROM chisel AS root-fs
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        # Bind mount from patched-release stage (and point `chisel cut --release` to this):
        --mount=type=bind,from=patched-release,src=/chisel-releases,target=/chisel-releases \
        # Also add the `socat_bins` slice to the list of slices to install:
        chisel cut --release /chisel-releases --root /root-fs \
          ... socat_bins

    Since the above example installs to /root-fs for a later COPY --link, instead of the bind mount, you could alternatively have an earlier COPY before RUN if you prefer. The bind mount is more useful if installing with chisel in a FROM scratch stage.

    So the it's 4 extra lines to the Dockerfile to support, plus your repo needs to commit the socat.yaml file (main drawback), until someone PRs the socat slice and it gets added to the official chisel-releases repo.

    Actually... since socat.yaml isn't too big, you could optionally embed it into the Dockerfile:

    # COPY socat.yaml /chisel-releases/slices/socat.yaml
    # Embed file content via HereDoc instead of external file copy:
    COPY <<HEREDOC /chisel-releases/slices/socat.yaml
    package: socat
    essential:
      - socat_copyright
    slices:
      bins:
        essential:
          - libc6_libs
          - libssl3t64_libs
          - libwrap0_libs
        contents:
          /usr/bin/socat:
          /usr/bin/socat1:
      copyright:
        contents:
          /usr/share/doc/socat/copyright:
    HEREDOC

    can't we just add (binary or package) it somehow?

    Potentially, depends how you go about it.

    There is no apt available, consider it similar to dnf/zypper ability to make a minimal install via --installroot but optimized at the expense of package selection.

    There is a slice for dpkg which can be used to install a .deb package AFAIK, but again that seems like a hassle?

    If the answer is no via apt it in another stage of the Dockerfile and later copy the binary?

    $ docker run --rm -it --env DEBIAN_FRONTEND=noninteractive ubuntu:25.04
    $ apt-get -qq update && apt-get -qq install socat
    
    $ ldd $(which socat)
            linux-vdso.so.1 (0x00007fc024772000)
            libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007fc02469e000)
            libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007fc024592000)
            libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007fc023e00000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc023a00000)
            libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc024574000)
            libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007fc024481000)
            /lib64/ld-linux-x86-64.so.2 (0x00007fc024774000)

    All those linked deps are supported by chisel, looks like with the slices already installed, we'd only need to add libwrap0_libs slice.

    EDIT: I have shared the approach of copying a binary from another stage/distro later with OpenSSL 3.5, you can refer to that for how that'd work. Or if you have a DEB package, you could likewise install that via dpkg I think (but you would need to install the dpkg_bins slice first).


    preferably it should include openssl 3.5.0

    TL;DR:

    • Not likely via package managers until roughly October? (Possibly earliest at May with Alpine 3.22)
    • I was successful with a workaround to transfer a copy from Debian Trixie to Chisel (Ubuntu 24.04) 👍

    OpenSSL 3.5.0 is a bit too new, may take a while to land to distros with release cycles.

    $ docker run --rm -it --env DEBIAN_FRONTEND=noninteractive ubuntu:25.04
    $ apt-get -qq update && apt-get -qq install openssl
    
    $ openssl -v
    OpenSSL 3.4.1 11 Feb 2025 (Library: OpenSSL 3.4.1 11 Feb 2025)
    # Debian 13 (Trixie) estimated release in 25H2, speculated August?:
    # https://www.phoronix.com/news/Debian-13-Freeze-Dates
    $ docker run --rm -it --env DEBIAN_FRONTEND=noninteractive debian:trixie
    $ apt-get -qq update && apt-get -qq install openssl
    
    $ openssl -v
    OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)
    
    # This is relevant for the later section that copies this build to another image:
    ldd $(which openssl)
            linux-vdso.so.1 (0x00007f514401b000)
            libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007f5143df6000)
            libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f5143600000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f514340a000)
            libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f5143dd6000)
            libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007f5143d0c000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f514401d000)
    ``
    
    ```console
    # Fedora 42 released April 2025:
    $ docker run --rm -it fedora:42
    $ dnf install -qy openssl
    
    $ openssl -v
    OpenSSL 3.2.4 11 Feb 2025 (Library: OpenSSL 3.2.4 11 Feb 2025)
    # Fedora 43 expected to release on Nov 2025:
    $ docker run --rm -it fedora:43
    $ dnf install -qy openssl
    
    $ openssl -v
    OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)
    # Alpine 3.22 expected in May?
    $ docker run --rm -it alpine:edge
    $ apk add openssl
    
    $ openssl -v
    OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)

    Copy OpenSSL 3.5 from Debian Trixie into Chisel image

    The openssl_bins slice installs the OpenSSL binary + related config files, it'll also bring in the required libs it links via separate slices notably libssl3t64.

    I suppose it's possible to keep size down by removing the main openssl binary itself in the same RUN and (with the current approach using a separate prep stage that creates /root-fs size won't matter here we can just COPY) copy over OpenSSL from another stage (Debian Trixie) that provides v3.5.0 🤔

    Earlier attempt with some troubleshooting towards solution
    FROM debian:trixie AS openssl-3.5
    ENV DEBIAN_FRONTEND=noninteractive
    RUN apt-get -qq update && apt-get -qq install openssl
    
    FROM chisel AS root-fs
    # ...
    # Add this to the end of this stage (this should work because shared deps appear compatible?):
    COPY --from=openssl-3.5 /usr/bin/openssl /root-fs/usr/bin/openssl

    Nope, that did not go well 😓

    $ docker build --tag localhost/testssl .
    
    $ docker run --rm -it --entrypoint '' localhost/testssl bash
    $ openssl -v
    openssl: /lib/x86_64-linux-gnu/libssl.so.3: version `OPENSSL_3.4.0' not found (required by openssl)
    openssl: /lib/x86_64-linux-gnu/libssl.so.3: version `OPENSSL_3.2.0' not found (required by openssl)
    openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.3.0' not found (required by openssl)
    openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.5.0' not found (required by openssl)
    openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.4.0' not found (required by openssl)
    openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.2.0' not found (required by openssl)

    Actually my mistake, this was due to Ubuntu 24.04 which shipped a much older OpenSSL (3.0), but we can see from the earlier error that there is still a dependency on 3.5.0 compatible libcrypto.so.3, so this won't be possible to support unless we copy over that as well.

    $ docker run --rm -it --env DEBIAN_FRONTEND=noninteractive ubuntu:24.04
    $ apt-get -qq update && apt-get -qq install openssl
    
    # NOTE: This version doesn't support `-v`
    $ openssl version
    OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)

    Even Ubuntu 24.10 lacks OpenSSL 3.4 and we can't use Ubuntu 25.04 with Chisel just yet, so libssl.so.3 would need to be copied over as well.

    $ docker run --rm -it --env DEBIAN_FRONTEND=noninteractive ubuntu:24.10
    $ apt-get -qq update && apt-get -qq install openssl
    
    $ openssl -v
    OpenSSL 3.3.1 4 Jun 2024 (Library: OpenSSL 3.3.1 4 Jun 2024)

    So let's give that a spin:

    FROM debian:trixie AS openssl-3.5
    ENV DEBIAN_FRONTEND=noninteractive
    RUN apt-get -qq update && apt-get -qq install openssl
    
    FROM chisel AS root-fs
    # ...
    # Add this to the end of this stage (this should work because shared deps appear compatible?):
    COPY --from=openssl-3.5 /usr/bin/openssl /root-fs/usr/bin/openssl
    COPY --from=openssl-3.5 /lib/x86_64-linux-gnu/libssl.so.3 /root-fs/lib/x86_64-linux-gnu/libssl.so.3
    COPY --from=openssl-3.5 /lib/x86_64-linux-gnu/libcrypto.so.3 /root-fs/lib/x86_64-linux-gnu/libcrypto.so.3
    $ docker build --tag localhost/testssl .
    
    $ docker run --rm -it --entrypoint '' localhost/testssl bash
    openssl -v
    OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)

    Success 🎉

  15. drwetter commented on Apr 30, 2025

    @drwetter
    Collaborator

    Success 🎉

    Sounds excellent! 👍👍 What's the size of that thing?

    (I have some comments to your comment but ... later. Need to get other stuff done first).

    Thanks!

  16. polarathene commented on Apr 30, 2025

    @polarathene
    ContributorAuthor

    TL;DR:

    • While your wish list was mostly possible with chisel, it's probably best to wait until "Future Goal" below is attainable.
    • BTW I don't really expect anyone to read these verbose comments. They're more of a technical reference.

    Quick overview:

    • Ubuntu / chisel do not support arm/v6 (armel) arch for packages.
    • chisel releases only publishes arm (arm/v7 / armhf I think), not armel which might prevent native builds on that legacy platform, but that's rather moot given that the OpenSSL 3.5 workaround armel package from Debian is incompatible with armhf installed packages from chisel / Ubuntu.
    • chisel releases also lacks a binary for i386, you'd need to either build that yourself or use the performance optimization to build each image variant without emulation.

    Reference - Future Goal

    No workarounds:

    • OpenSSL 3.5 via openssl_bins (25.10?)
    • socat_bins slice officially added
    • Official chisel image published for supported archs
    • ⚠ Likely drop legacy i386 + arm/v6 archs
    # syntax=docker/dockerfile:1
    
    # Prepare root-fs with required packages:
    FROM chisel AS root-fs
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        chisel cut --release ubuntu-25.10 --root /root-fs \
            base-files_base bash_bins procps_bins grep_bins gawk_bins sed_bins \
            coreutils_bins busybox_bins openssl_bins curl_bins dash_bins socat_bins
    
    # Final size optimized stage to publish to image registries:
    FROM scratch
    COPY --link --from=root-fs /root-fs /
    RUN <<HEREDOC
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl, roughly equivalent to this `useradd` command:
        #useradd --uid 1000 --user-group --create-home --shell /bin/bash testssl
        echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
        echo 'testssl:x:1000:' >> /etc/group
        install --mode 2755 --owner testssl --group testssl --directory /home/testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        mkdir -p /usr/local/bin
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    # Copy over build context (after filtered by .dockerignore): bin/ etc/ testssl.sh
    COPY --chown=testssl:testssl . /home/testssl/
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]

    Reference - Full Dockerfiles (workarounds)

    Before/After reference with all the details covered in prior comment vs this comment.

    # Creating a container driver for Buildx to build with:
    docker buildx create --name=container --driver=docker-container --bootstrap --use
    
    # Now we can build multiple platforms concurrently:
    docker buildx build \
      --builder=container \
      --platform=linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/386,linux/ppc64le \
      --tag localhost/testssl \
      .
    Earlier `Dockerfile` iteration (where the previous comment reached)

    This was what I had in my prior comment, but the OpenSSL 3.5 workaround was specific to AMD64 / x86_64 arch. Building for non-native archs also was notably slower (presumably the case with existing setup in CI), but less verbose.

    # syntax=docker/dockerfile:1
    
    # NOTE: This will be redundant once an official chisel image is released:
    FROM alpine AS chisel
    ARG CHISEL_VERSION=1.1.0
    ARG TARGETARCH
    ARG CHISEL_RELEASE="https://github.com/canonical/chisel/releases/download/v${CHISEL_VERSION}/chisel_v${CHISEL_VERSION}_linux_${TARGETARCH}.tar.gz"
    RUN wget -qO - "${CHISEL_RELEASE}" | tar -xz --no-same-owner -C /usr/local/bin chisel
    
    # WORKAROUND A: Until chisel adds a slice for socat
    FROM scratch AS patched-release
    ADD https://github.com/canonical/chisel-releases.git#ubuntu-24.04 /chisel-releases
    COPY <<HEREDOC /chisel-releases/slices/socat.yaml
    package: socat
    essential:
      - socat_copyright
    slices:
      bins:
        essential:
          - libc6_libs
          - libssl3t64_libs
          - libwrap0_libs
        contents:
          /usr/bin/socat:
          /usr/bin/socat1:
      copyright:
        contents:
          /usr/share/doc/socat/copyright:
    HEREDOC
    
    # WORKAROUND B: Until chisel supports Ubuntu 25.10 with OpenSSL 3.5+
    FROM debian:trixie AS openssl-3.5
    ENV DEBIAN_FRONTEND=noninteractive
    RUN apt-get -qq update && apt-get -qq install openssl
    
    # Prepare root-fs with required packages:
    FROM chisel AS root-fs
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        --mount=type=bind,from=patched-release,src=/chisel-releases,target=/chisel-releases \
        chisel cut --release /chisel-releases --root /root-fs \
            base-files_base bash_bins procps_bins grep_bins gawk_bins sed_bins \
            coreutils_bins busybox_bins openssl_bins curl_bins dash_bins socat_bins
    # WORKAROUND B:
    COPY --from=openssl-3.5 /usr/bin/openssl /root-fs/usr/bin/openssl
    COPY --from=openssl-3.5 /lib/x86_64-linux-gnu/libssl.so.3 /root-fs/lib/x86_64-linux-gnu/libssl.so.3
    COPY --from=openssl-3.5 /lib/x86_64-linux-gnu/libcrypto.so.3 /root-fs/lib/x86_64-linux-gnu/libcrypto.so.3
    
    # Final size optimized stage to publish to image registries:
    FROM scratch
    COPY --link --from=root-fs /root-fs /
    RUN <<HEREDOC
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl, roughly equivalent to this `useradd` command:
        #useradd --uid 1000 --user-group --create-home --shell /bin/bash testssl
        echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
        echo 'testssl:x:1000:' >> /etc/group
        install --mode 2755 --owner testssl --group testssl --directory /home/testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        mkdir -p /usr/local/bin
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    
    # Copy over build context (after filtered by .dockerignore): bin/ etc/ testssl.sh
    COPY --chown=testssl:testssl . /home/testssl/
    
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]

    So this was quite the test of my skillset in pulling all this off 😓

    Obviously it's far less appealing to adopt with the workarounds. May be best to wait until later this year.

    The earlier iteration above could be used (without the OpenSSL 3.5 workaround), which leaves the only benefit below as faster image builds (for platforms that don't match the build host) but I doubt that matters much for this project vs simplicity? 😝 (seeing that you've been fine without such an optimization so far)

    # syntax=docker/dockerfile:1
    
    # NOTE: This will be redundant once an official chisel image is released:
    FROM --platform=${BUILDPLATFORM} alpine AS chisel
    ARG CHISEL_VERSION=1.1.0
    ARG BUILDARCH
    ARG CHISEL_RELEASE="https://github.com/canonical/chisel/releases/download/v${CHISEL_VERSION}/chisel_v${CHISEL_VERSION}_linux_${BUILDARCH}.tar.gz"
    RUN wget -qO - "${CHISEL_RELEASE}" | tar -xz --no-same-owner -C /usr/local/bin chisel
    
    
    # WORKAROUND A: Until chisel adds a slice for socat
    FROM scratch AS patched-release
    ADD https://github.com/canonical/chisel-releases.git#ubuntu-24.04 /chisel-releases
    COPY <<HEREDOC /chisel-releases/slices/socat.yaml
    package: socat
    essential:
      - socat_copyright
    slices:
      bins:
        essential:
          - libc6_libs
          - libssl3t64_libs
          - libwrap0_libs
        contents:
          /usr/bin/socat:
          /usr/bin/socat1:
      copyright:
        contents:
          /usr/share/doc/socat/copyright:
    HEREDOC
    
    
    # Workaround B - Performance improvement (avoid emulation for non-native image builds)
    # Map TARGETPLATFORM from BuildKit to equivalent input for `chisel cut --arch`:
    FROM --platform=${BUILDPLATFORM} alpine AS chisel-arch-target
    ARG TARGETPLATFORM
    RUN <<HEREDOC
      case "${TARGETPLATFORM}" in
        ( 'linux/amd64' )
          echo 'amd64' > /target-arch.txt
          ;;
        ( 'linux/arm64' )
          echo 'arm64' > /target-arch.txt
          ;;
        ( 'linux/arm/v7' )
          echo 'armhf' > /target-arch.txt
          ;;
        ( 'linux/arm/v6' )
          # NOTE: This probably isn't reliable and should be `armel` if Ubuntu supported it:
          echo 'armhf' > /target-arch.txt
          ;;
        ( 'linux/386' )
          echo 'i386' > /target-arch.txt
          ;;
        ( 'linux/ppc64le' )
          echo 'ppc64el' > /target-arch.txt
          ;;
        ( * )
          echo "ERROR: Unsupported target platform: '${TARGETPLATFORM}'"
          return 1
          ;;
      esac
    HEREDOC
    
    
    # WORKAROUND C: Until chisel supports Ubuntu 25.10 with OpenSSL 3.5+
    FROM --platform=${BUILDPLATFORM} debian:trixie AS openssl-3.5
    ENV DEBIAN_FRONTEND=noninteractive
    COPY --link --from=chisel-arch-target /target-arch.txt /target-arch.txt
    RUN <<HEREDOC
      dpkg --add-architecture "$(cat /target-arch.txt)"
      apt-get -qq update
      apt-get -qq install "openssl:$(cat /target-arch.txt)"
    HEREDOC
    # Variants required for later to get the correct lib arch path to COPY:
    FROM chisel AS chisel-arm64
    ENV TARGET_TRIPLE=aarch64-linux-gnu
    FROM chisel AS chisel-amd64
    ENV TARGET_TRIPLE=x86_64-linux-gnu
    FROM chisel AS chisel-armv7
    ENV TARGET_TRIPLE=arm-linux-gnueabihf
    FROM chisel AS chisel-armv6
    ENV TARGET_TRIPLE=arm-linux-gnueabi
    FROM chisel AS chisel-386
    ENV TARGET_TRIPLE=i386-linux-gnu
    FROM chisel AS chisel-ppc64le
    ENV TARGET_TRIPLE=powerpc64le-linux-gnu
    
    
    # Prepare root-fs with required packages:
    FROM chisel-${TARGETARCH}${TARGETVARIANT} AS root-fs
    COPY --link --from=chisel-arch-target /target-arch.txt /target-arch.txt
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        --mount=type=bind,from=patched-release,src=/chisel-releases,target=/chisel-releases \
        chisel cut --arch "$(cat /target-arch.txt)" --release /chisel-releases --root /root-fs \
            base-files_base bash_bins procps_bins grep_bins gawk_bins sed_bins \
            coreutils_bins busybox_bins openssl_bins curl_bins dash_bins socat_bins
    # WORKAROUND C support:
    COPY --from=openssl-3.5 /usr/bin/openssl /root-fs/usr/bin/openssl
    COPY --from=openssl-3.5 /usr/lib/${TARGET_TRIPLE}/libssl.so.3 /root-fs/usr/lib/${TARGET_TRIPLE}/libssl.so.3
    COPY --from=openssl-3.5 /usr/lib/${TARGET_TRIPLE}/libcrypto.so.3 /root-fs/usr/lib/${TARGET_TRIPLE}/libcrypto.so.3
    
    
    # Final size optimized stage to publish to image registries:
    FROM scratch
    COPY --link --from=root-fs /root-fs /
    RUN <<HEREDOC
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl, roughly equivalent to this `useradd` command:
        #useradd --uid 1000 --user-group --create-home --shell /bin/bash testssl
        echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
        echo 'testssl:x:1000:' >> /etc/group
        install --mode 2755 --owner testssl --group testssl --directory /home/testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        mkdir -p /usr/local/bin
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    
    # Copy over build context (after filtered by .dockerignore): bin/ etc/ testssl.sh
    COPY --chown=testssl:testssl . /home/testssl/
    
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]

    If helpful here is the above improved Dockerfile but with only one of the workarounds applied:

    Cross-platform OpenSSL 3.5 workaround (without native build speed optimization)

    NOTE: This Dockerfile cannot build i386 arch due to chisel lacking a release for it.

    # syntax=docker/dockerfile:1
    
    # NOTE: This will be redundant once an official chisel image is released:
    FROM alpine AS chisel
    ARG CHISEL_VERSION=1.1.0
    ARG TARGETARCH
    ARG CHISEL_RELEASE="https://github.com/canonical/chisel/releases/download/v${CHISEL_VERSION}/chisel_v${CHISEL_VERSION}_linux_${TARGETARCH}.tar.gz"
    RUN wget -qO - "${CHISEL_RELEASE}" | tar -xz --no-same-owner -C /usr/local/bin chisel
    
    
    # WORKAROUND A: Until chisel adds a slice for socat
    FROM scratch AS patched-release
    ADD https://github.com/canonical/chisel-releases.git#ubuntu-24.04 /chisel-releases
    COPY <<HEREDOC /chisel-releases/slices/socat.yaml
    package: socat
    essential:
      - socat_copyright
    slices:
      bins:
        essential:
          - libc6_libs
          - libssl3t64_libs
          - libwrap0_libs
        contents:
          /usr/bin/socat:
          /usr/bin/socat1:
      copyright:
        contents:
          /usr/share/doc/socat/copyright:
    HEREDOC
    
    
    # WORKAROUND B: Until chisel supports Ubuntu 25.10 with OpenSSL 3.5+
    FROM debian:trixie AS openssl-3.5
    ENV DEBIAN_FRONTEND=noninteractive
    RUN <<HEREDOC
      apt-get -qq update
      apt-get -qq install "openssl"
    HEREDOC
    # Variants required for later to get the correct lib arch path to COPY:
    FROM chisel AS chisel-arm64
    ENV TARGET_TRIPLE=aarch64-linux-gnu
    FROM chisel AS chisel-amd64
    ENV TARGET_TRIPLE=x86_64-linux-gnu
    FROM chisel AS chisel-armv7
    ENV TARGET_TRIPLE=arm-linux-gnueabihf
    FROM chisel AS chisel-armv6
    ENV TARGET_TRIPLE=arm-linux-gnueabi
    FROM chisel AS chisel-386
    ENV TARGET_TRIPLE=i386-linux-gnu
    FROM chisel AS chisel-ppc64le
    ENV TARGET_TRIPLE=powerpc64le-linux-gnu
    
    
    # Prepare root-fs with required packages:
    FROM chisel-${TARGETARCH}${TARGETVARIANT} AS root-fs
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        --mount=type=bind,from=patched-release,src=/chisel-releases,target=/chisel-releases \
        chisel cut --release /chisel-releases --root /root-fs \
            base-files_base bash_bins procps_bins grep_bins gawk_bins sed_bins \
            coreutils_bins busybox_bins openssl_bins curl_bins dash_bins socat_bins
    # WORKAROUND B support:
    COPY --from=openssl-3.5 /usr/bin/openssl /root-fs/usr/bin/openssl
    COPY --from=openssl-3.5 /usr/lib/${TARGET_TRIPLE}/libssl.so.3 /root-fs/usr/lib/${TARGET_TRIPLE}/libssl.so.3
    COPY --from=openssl-3.5 /usr/lib/${TARGET_TRIPLE}/libcrypto.so.3 /root-fs/usr/lib/${TARGET_TRIPLE}/libcrypto.so.3
    
    
    # Final size optimized stage to publish to image registries:
    FROM scratch
    COPY --link --from=root-fs /root-fs /
    RUN <<HEREDOC
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl, roughly equivalent to this `useradd` command:
        #useradd --uid 1000 --user-group --create-home --shell /bin/bash testssl
        echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
        echo 'testssl:x:1000:' >> /etc/group
        install --mode 2755 --owner testssl --group testssl --directory /home/testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        mkdir -p /usr/local/bin
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    
    # Copy over build context (after filtered by .dockerignore): bin/ etc/ testssl.sh
    COPY --chown=testssl:testssl . /home/testssl/
    
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]

    This one is needed for i386 support:

    Native build speed optimization (without OpenSSL 3.5 workaround)
    # syntax=docker/dockerfile:1
    
    # NOTE: This will be redundant once an official chisel image is released:
    FROM --platform=${BUILDPLATFORM} alpine AS chisel
    ARG CHISEL_VERSION=1.1.0
    ARG BUILDARCH
    ARG CHISEL_RELEASE="https://github.com/canonical/chisel/releases/download/v${CHISEL_VERSION}/chisel_v${CHISEL_VERSION}_linux_${BUILDARCH}.tar.gz"
    RUN wget -qO - "${CHISEL_RELEASE}" | tar -xz --no-same-owner -C /usr/local/bin chisel
    
    
    # WORKAROUND A: Until chisel adds a slice for socat
    FROM scratch AS patched-release
    ADD https://github.com/canonical/chisel-releases.git#ubuntu-24.04 /chisel-releases
    COPY <<HEREDOC /chisel-releases/slices/socat.yaml
    package: socat
    essential:
      - socat_copyright
    slices:
      bins:
        essential:
          - libc6_libs
          - libssl3t64_libs
          - libwrap0_libs
        contents:
          /usr/bin/socat:
          /usr/bin/socat1:
      copyright:
        contents:
          /usr/share/doc/socat/copyright:
    HEREDOC
    
    
    # Workaround B - Performance improvement (avoid emulation for non-native image builds)
    # Map TARGETPLATFORM from BuildKit to equivalent input for `chisel cut --arch`:
    FROM --platform=${BUILDPLATFORM} alpine AS chisel-arch-target
    ARG TARGETPLATFORM
    RUN <<HEREDOC
      case "${TARGETPLATFORM}" in
        ( 'linux/amd64' )
          echo 'amd64' > /target-arch.txt
          ;;
        ( 'linux/arm64' )
          echo 'arm64' > /target-arch.txt
          ;;
        ( 'linux/arm/v7' )
          echo 'armhf' > /target-arch.txt
          ;;
        ( 'linux/arm/v6' )
          # NOTE: This probably isn't reliable and should be `armel` if Ubuntu supported it:
          echo 'armhf' > /target-arch.txt
          ;;
        ( 'linux/386' )
          echo 'i386' > /target-arch.txt
          ;;
        ( 'linux/ppc64le' )
          echo 'ppc64el' > /target-arch.txt
          ;;
        ( * )
          echo "ERROR: Unsupported target platform: '${TARGETPLATFORM}'"
          return 1
          ;;
      esac
    HEREDOC
    
    
    # Prepare root-fs with required packages:
    FROM chisel-${TARGETARCH}${TARGETVARIANT} AS root-fs
    COPY --link --from=chisel-arch-target /target-arch.txt /target-arch.txt
    WORKDIR /root-fs
    RUN --mount=type=cache,target=/.cache/chisel,id="chisel-cache" \
        --mount=type=bind,from=patched-release,src=/chisel-releases,target=/chisel-releases \
        chisel cut --arch "$(cat /target-arch.txt)" --release /chisel-releases --root /root-fs \
            base-files_base bash_bins procps_bins grep_bins gawk_bins sed_bins \
            coreutils_bins busybox_bins openssl_bins curl_bins dash_bins socat_bins
    
    
    # Final size optimized stage to publish to image registries:
    FROM scratch
    COPY --link --from=root-fs /root-fs /
    RUN <<HEREDOC
        # Creates symlinks for any other commands that busybox can provide
        # that aren't already provisioned via coreutils (notably hexdump + tar, see #2403):
        busybox --install -s
    
        # Add a non-root user testssl, roughly equivalent to this `useradd` command:
        #useradd --uid 1000 --user-group --create-home --shell /bin/bash testssl
        echo 'testssl:x:1000:1000::/home/testssl:/bin/bash' >> /etc/passwd
        echo 'testssl:x:1000:' >> /etc/group
        install --mode 2755 --owner testssl --group testssl --directory /home/testssl
    
        # Symlink a `testssl.sh` command to the home dir that it'll be installed to:
        mkdir -p /usr/local/bin
        ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh
    HEREDOC
    
    # Copy over build context (after filtered by .dockerignore): bin/ etc/ testssl.sh
    COPY --chown=testssl:testssl . /home/testssl/
    
    USER testssl
    ENTRYPOINT ["testssl.sh"]
    CMD ["--help"]

    Supporting other Architectures

    Ubuntu package archives with supported architectures between these two repos:

    Debian doesn't have this split.

    If needing to add packages for foreign architectures with Ubuntu you will need to update /etc/apt/sources.list.d/ubuntu.sources by appending this (add a similar Architectures constraint to existing repos for i386):

    Types: deb
    URIs: http://ports.ubuntu.com/ubuntu-ports/
    Suites: noble noble-updates noble-backports noble-security
    Components: main universe restricted multiverse
    Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
    Architectures: arm64,armhf,ppc64el
    

    NOTE: ⚠ There is no linux/arm/v6 (armel) arch for the ubuntu images, only linux/arm/v7 (armhf). Debian has support for arm/v6 / armel.

    The definition for armhf and armel varies:

    Some state there is a bit of compatibility between v6 and v7 via armhf, provided no v7 specific features are used? 🤷‍♂

    There's also this Alpine discussion from May 2020 about dropping armv6, but noting Pi Zero W (armv6 based) is supported until Jan 2026.

    Regardless, you can see it's more of a maintenance hassle when it's not implicit. Since this is a CI specific concern and for your project presumably only relevant with publishing releases, then optimizing speed of the building for platforms not native to the build host arch is unlikely a concern for you to worry about 😅

    The OpenSSL workaround however would require this until October-ish for Ubuntu 25.10, when you should be able to remove it.

    OpenSSL 3.5 workaround support

    The OpenSSL workaround is a bit problematic when spanning multiple platforms due to arch specific filesystem differences for where dependencies are stored.

    The arch used will install OpenSSL libs into /usr/lib/ with an arch specific directory:

    • amd64 => x86_64-linux-gnu
    • arm64 => aarch64-linux-gnu
    • armhf (Docker target: arm/v7) => arm-linux-gnueabihf
    • armel (Docker target: arm/v6) => arm-linux-gnueabi (EDIT: Not supported on Ubuntu/Chisel, only Debian)
    • i386 => (Docker target: 386) => i386-linux-gnu
    • ppc64el (Docker target: ppc64le) => powerpc64le-linux-gnu

    TIP: When building each platfrom, if it's running native/emulated:

    • You can get the appropriate platform arch for apt or chisel via dpkg --print-architecture. Similar to how you might use uname -m or TARGETARCH + TARGETVARIANT.
    • You can list each other arch not native to the image platform (FROM --platform, or inferred) via dpkg --print-foreign-architectures. Those will be any other archs you've added via dpkg --add-architecture.

    There's two common conventions for architecture names, but sometimes it varies between projects a little bit too 😕 (here's a helpful resource on the topic) We would need to map these arch to the toolchain value like shown above, or provide them via an ARG per platform (tad awkward).

    Easiest way to workaround that within a Dockerfile is probably going to be setting ENV with a bunch of platform specific stages. Naming each stage variant with a suffix that we can refer to via the appropriate TARGETARCH value (list of the mappings here).

    FROM chisel AS chisel-arm64
    ENV TARGET_TRIPLE=aarch64-linux-gnu
    
    FROM chisel AS chisel-amd64
    ENV TARGET_TRIPLE=x86_64-linux-gnu
    
    FROM chisel AS chisel-armv7
    ENV TARGET_TRIPLE=arm-linux-gnueabihf
    
    FROM chisel AS chisel-armv6
    ENV TARGET_TRIPLE=arm-linux-gnueabi
    
    FROM chisel AS chisel-386
    ENV TARGET_TRIPLE=i386-linux-gnu
    
    FROM chisel AS chisel-ppc64el
    ENV TARGET_TRIPLE=powerpc64le-linux-gnu

    Then update the two OpenSSL library COPY to reference the TARGET_TRIPLE ENV and adjust the stage to use the arch specific variants:

    ARG TARGETARCH
    FROM chisel-${TARGETARCH}${TARGETVARIANT} AS root-fs
    # ...
    COPY --from=openssl-3.5 /usr/lib/${TARGET_TRIPLE}/libssl.so.3 /root-fs/usr/lib/${TARGET_TRIPLE}/libssl.so.3
    COPY --from=openssl-3.5 /usr/lib/${TARGET_TRIPLE}/libcrypto.so.3 /root-fs/usr/lib/${TARGET_TRIPLE}/libcrypto.so.3

    chisel-${TARGETARCH}${TARGETVARIANT} is where Docker will work backwards in stages of what to build, so it can get a bit confusing at a glance with how it infers the the right arch (especially when paired with the performance changes in the next section).

    NOTE: TARGETVARIANT will be blank for most platform targets, but for arm/v6 and arm/v7 it's the v6 / v7 parts. Similarly there is amd64/v4 variant and lower to match the different x86-64-v4 optimized builds some projects build (Debian doesn't have optimized package repos for such, but some other distros do like CachyOS).


    UPDATE: I've verified that arm/v6 (armel) is not compatible with the armhf package arch with Ubuntu / chisel, it'd fail to run the armel OpenSSL 3.5 from Debian that links to a different interpreter:

    # Build and load this arch into Docker host for running:
    $ docker buildx build --builder=container --platform=linux/arm/v6 --tag local/testssl --load .
    
    # If `docker run --platform ...` fails for you, you're missing a required QEMU package:
    # dnf install qemu-user-static
    $ docker run --rm -it --platform=linux/arm/v6 --entrypoint '' localhost/testssl openssl -v
    qemu-arm-static: Could not open '/lib/ld-linux.so.3': No such file or directory
    
    $ docker run --rm -it --platform=linux/arm/v6 --entrypoint '' localhost/testssl ls /lib
    arm-linux-gnueabi  arm-linux-gnueabihf  ld-linux-armhf.so.3  ssl

    So that rules out that platform with chisel..


    Reference - Image platforms + variants published

    So there is linux/amd64, linux/arm64, linux/ppc64le, linux/s390x but no linux/386, linux/arm/v7, linux/arm/v6
    I have no clue about the usage of the usage of 386, arm/v7, arm/v6.

    #2422 (comment)

    BTW regarding image arch support on DockerHub for official images, there is this official document. Probably why you may find inconsistencies with the images published to GHCR (which aren't managed indirectly like with DockerHub review process + policies for official/endorsed images).

    That or I misunderstood it 😅 (I think the only reason the arch's listed there may not be supported by such images is if they lack package manager support, such as Ubuntu lacking linux/arm/v6).

    EDIT: Slight mixup also on my end. I was thinking of DockerHub vs GHCR, but the issue from #2748 was actually the switch between the two Dockerfile moving from Alpine to openSUSE Leap 😅

    openSUSE Leap 15.6 was published to DockerHub without linux/arm (armhf / arm/v7) variant.
    -Currently the latest tag points all archs to 15.6 platform images, but still retains the linux/arm platform pointed to Leap 15.5 😬

    • The limited selection seems to align with their package repo archs, I don't see armv7 / armhf or similar at the equivalent 15.5 repo, so not quite sure where that arch was sourced from in their published 15.5 image.

    Meanwhile debian:12-slim and other image tags don't have a linux/arm or linux/arm/v6, there's only linux/arm/v5 and linux/arm/v7 (also a linux/arm64/v8 but AFAIK that's equivalent to linux/arm64, except v8 will exist as a TARGETVARIANT with the long form, I guess the short-form selects the lowest variant 🤷‍♂). When I pull --platform linux/arm/v6 or --platform linux/arm and inspect that image it's the linux/arm/v5 image. Perhaps armv5 is compatible with armv6 🤔

    Tip - Performance with chisel

    EDIT: I somehow forgot about image layer caching with CI 😂 Ah well, I'll keep this here since the discussion thread is full of insights for any interested readers.

    When building an image for non-native targets (compared to the Docker build host), these will use virtualization/emulation and depending on operations in RUN can be much slower to perform.

    On a budget VPS instance these two examples stood out where ARM64 is emulated via QEMU:

    • RUN chisel cut --release ... => 65s (AMD64) vs 277s (ARM64)
    • RUN apt-get -qq update && apt-get -qq install openssl => 12s (AMD64) vs 170s (ARM64)

    Concurrent image builds can be faster than sequential, it's what you have the CI performing:

    https://github.com/testssl/testssl.sh/blob/3a9746ccc5714d9b151d942a61652ad9fa8b6cd3/.github/workflows/docker-3.2.yml#L56C22-L56C95

    UPDATE:

    • For comparison I was able to build the full set of platforms you currently support (except for arm/v6) all within 260s (--no-cache was used, 206s without i386) on that same build host (Vultr Fedora 42 with 1vCPU and 1GB RAM + zram swap). While the concurrent image builds for platforms (linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le) via emulation (QEMU) instead took 1,260s (21 minutes). The low spec resources probably ramp those times up a bit 😓 (you also have CI cache leveraged, making most of this benefit irrelevant)
    • Without this solution, the official chisel binary available is missing legacy archs, at least with i386 and possibly one of arm/v6 / arm/v7 (or they might both be supported by arm?). So this is another benefit of only pulling a single arch of chisel to build images for all other platforms.
      • The kernel will or has dropped i386 support I think? There is also this Debian mailing list thread (Dec 2023) that talks about dropping support in future. I get wanting to support extra archs when it's easy for you to do so, but this is presumably going to get more tricky going forward as this entire comment hopefully illustrates 😓
      • You'd probably find the two arm images the Dockerfile builds might even be effectively the same in content? So while TARGETARCH will provide arm for selecting the chisel binary, and with assistance armhf for packages, we're effectively building the same image twice and tagging it for both of these legacy platforms 🤷‍♂

    We can run the native build host arch instead when the operations we need to run do not require emulating an architecture to prepare the image, such as cross-platform builds or in this case a package manager like chisel that supports installing for non-native archs via --arch option.

    We need to provide chisel cut --arch with the appropriate Ubuntu arch, which luckily many of these align with the TARGETARCH ENV that BuildKit sets implicitly as an ARG, except for legacy arm/v6 + arm/v7, where we'd need to instead provide armhf.

    We would need to map the appropriate --arch value from TARGETARCH ENV, at least for legacy platforms you still want to support i386 + armv6 + armv7:

    # Map TARGETPLATFORM from BuildKit to equivalent input for `chisel cut --arch`:
    FROM alpine AS chisel-arch-target
    ARG TARGETPLATFORM
    RUN <<HEREDOC
      case "${TARGETPLATFORM}" in
        ( 'linux/amd64' )
          echo 'amd64' > /target-arch.txt
          ;;
        ( 'linux/arm64' )
          echo 'arm64' > /target-arch.txt
          ;;
        ( 'linux/arm/v7' )
          echo 'armhf' > /target-arch.txt
          ;;
        ( 'linux/arm/v6' )
          # NOTE: This probably isn't reliable and should be `armel` if Ubuntu supported it:
          echo 'armhf' > /target-arch.txt
          ;;
        ( 'linux/386' )
          echo 'i386' > /target-arch.txt
          ;;
        ( 'linux/ppc64le' )
          echo 'ppc64el' > /target-arch.txt
          ;;
        ( * )
          echo "ERROR: Unsupported target platform: '${TARGETPLATFORM}'"
          return 1
          ;;
      esac
    HEREDOC

    EDIT: Turns out armhf is arm/v7 only. I haven't tried running that as arm/v6 (armel) properly without OpenSSL 3.5 armel copied from from Debian. Assuming incompatible.

    Then in the RUN that calls chisel cut we just add --arch $(cat /target-arch.txt) and add an extra COPY prior to that RUN.

    FROM --platform=${BUILDPLATFORM} chisel AS root-fs
    COPY --link --from=chisel-arch-target /target-arch.txt /target-arch.txt
    # ... RUN chisel cut --arch "$(cat /target-arch.txt)"

    Since chisel isn't an official image yet, we'd actually want that --platform option set on our temporary chisel stage instead. And since we want to run the build host arch, we should swap the TARGETARCH arg for BUILDARCH.

    # NOTE: This will be redundant once an official chisel image is released:
    FROM --platform=${BUILDPLATFORM} alpine AS chisel
    ARG CHISEL_VERSION=1.1.0
    ARG BUILDARCH
    ARG CHISEL_RELEASE="https://github.com/canonical/chisel/releases/download/v${CHISEL_VERSION}/chisel_v${CHISEL_VERSION}_linux_${BUILDARCH}.tar.gz"
    RUN wget -qO - "${CHISEL_RELEASE}" | tar -xz --no-same-owner -C /usr/local/bin chisel

    EDIT: This makes i386 supported 👍 (that legacy arch is not published on GH releases)

    I believe a similar approach can be used for apt to get openssl in Debian (same arch names, bit broader in support, but no riscv64 - maybe with Debian Trixie). Since they have the same arch convention as Ubuntu, we can use the same target-arch.txt with apt as we do chisel:

    FROM --platform=${BUILDPLATFORM} debian:trixie AS openssl-3.5
    ENV DEBIAN_FRONTEND=noninteractive
    COPY --link --from=chisel-arch-target /target-arch.txt /target-arch.txt
    RUN <<HEREDOC
      dpkg --add-architecture "$(cat /target-arch.txt)"
      apt-get -qq update
      apt-get -qq install "openssl:$(cat /target-arch.txt)"
    HEREDOC
  17. polarathene commented on Apr 30, 2025

    @polarathene
    ContributorAuthor

    What's the size of that thing?

    55MB (with OpenSSL 3.5 added)

    Same as before mostly, for some reason docker image ls is showing 77MB, but dive reports 45MB + 8MB which is similar to docker image history:

    $ docker image history localhost/testssl
    IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
    3230df592e4a   32 minutes ago   CMD ["--help"]                                  0B        buildkit.dockerfile.v0
    <missing>      32 minutes ago   ENTRYPOINT ["testssl.sh"]                       0B        buildkit.dockerfile.v0
    <missing>      32 minutes ago   USER testssl                                    0B        buildkit.dockerfile.v0
    <missing>      32 minutes ago   COPY --chown=testssl:testssl . /home/testssl…   8.21MB    buildkit.dockerfile.v0
    <missing>      32 minutes ago   RUN /bin/sh -c     # Creates symlinks for an…   57.3kB    buildkit.dockerfile.v0
    <missing>      32 minutes ago   COPY /root-fs / # buildkit                      46.7MB    buildkit.dockerfile.v0

    So 55MB?

    A little larger than the openSUSE Leap 15.6 image. No advantage to switching beyond broader arch support. Without OpenSSL 3.5 + libs copied over, it is 1.7MB smaller.


    Regarding performance of musl base image (Alpine) vs glibc base image (openSUSE Leap), several runs invoked:

    # NOTE: Without `--openssl`, uses `/home/testssl/bin/openssl.Linux.x86_64`. glibc distro unaffected, musl runs slower.
    
    # 116s - Chisel (Ubuntu 24.04)
    docker run --rm -it localhost/testssl.sh:3.2 --openssl /usr/bin/openssl google.com
    
    # 167s - Alpine (188s without `--openssl`)
    docker run --rm -it ghcr.io/testssl/testssl.sh:3.2 --openssl /usr/bin/openssl google.com
    
    # 136s - openSUSE Leap
    docker run --rm -it docker.io/drwetter/testssl.sh:3.2 --openssl /usr/bin/openssl google.com

    Alpine remains slower. These were compared back in early 2023 when discussing what to replace Alpine with.


    Additional packages (DNS)

    I just noticed I missed some packages related to DNS.

    bind9-dnsutils_bins (also provides the libidn2-0_libs slice), which brings in libidn + dig (instead of drill which lacks a slice, openSUSE Leap provided drill via the ldns package).

    • It seems that when the image was Debian based it had dig until switching over to only drill, and with the Alpine base it added in libidn for drill to support IDN lookups. So that is one drawback if another DNS client can't be used for that? FWIW drill / ldns is in maintenance mode.
    • In my Feb 2023 PR that refactored the Dockerfile when it was Alpine based, I provide a good overview of each package requirement. Presumably busybox_bins could be dropped if tar (via tar_tar slice) and xxd (no slice yet) were available. Not sure if busybox is otherwise providing anything else testssl.sh would need that coreutils doesn't 🤷‍♂
    • UPDATE:
      • busybox adds symlinks not only for hexdump, but also xxd (optional, but apparently performant). For DNS it also adds nslookup + hostname.

      • The curl_bins slice also brings in the libidn2-0_libs slice, so the bind9-dnsutils_bins (adds 47MB weight) slice is only necessary if needing dig, not space efficient. Bringing dig in like I showed with OpenSSL would not be reasonable (far too large of a list), but drill is one extra library libldns + symlink, so should be doable (only +480KB weight), can probably just use a custom slice for that actually (EDIT: I've created one here, it could be inlined with COPY like the socat slice example I provided).

      • Likewise here is an xxd slice. I haven't opened an issue upstream to request it as there's barely any size bonus from replacing busybox_bins slice with xxd_bins (23KB) + tar_tar (432KB), as busybox binary itself is only about 847KB:

        xxd slice
        COPY <<HEREDOC /chisel-releases/slices/xxd.yaml
        package: xxd
        essential:
          - xxd_copyright
        slices:
          bins:
            essential:
              - libc6_libs
            contents:
              /usr/bin/xxd:
          copyright:
            contents:
              /usr/share/doc/xxd/copyright:
        HEREDOC

        xxd might be optional, but hexdump (48KB from bsdmainutils, no slice available) apparently isn't 😓 so busybox remains.

        $ docker run --rm -it localhost/testssl --openssl /usr/bin/openssl google.com
        
        Fatal error: You need to install hexdump for this program to work

    bash procps grep gawk sed coreutils busybox ldns libidn2-0 socat openssl curl \

    && apk add --no-cache bash procps drill git coreutils libidn curl socat openssl xxd \

    For reference:


    Tip - Dockerfile vs Dockerfile.git

    You could effectively have a Dockerfile.git replacement by swapping:

    COPY --chown=testssl:testssl . /home/testssl/

    With:

    ARG GIT_BRANCH=3.2
    ARG GIT_URL=https://github.com/testssl/testssl.sh.git
    ADD --chown=testssl:testssl ${GIT_URL}#${GIT_BRANCH?branch-required} /home/testssl

    ADD is a useful Dockerfile instruction mostly similar to COPY, but in this case it allows us to replace the manual git clone invoked in a RUN:

    && git clone --depth 1 --branch ${BUILD_VERSION} $URL /home/testssl \

    That's effectively the same as doing a git clone, although it'd be a bit lighter with ADD:

    • The default behaviour for ADD with git repos is to exclude the .git/ directory (has a weight of 6.7MB when I built it, 9.3MB in current GHCR image).
    • No need for a git package installed to support this either 👍 (8MB less)

    A caveat with cloning a repo in a Dockerfile (regardless of git clone or ADD ... approach) is that it won't be part of the Docker build context (which filters the local host filesystem via .dockerignore), that's only relevant when using ADD/COPY with the local host filesystem.

    Apart from differences between distros, this is really the only difference between your Dockerfile vs Dockerfile.git support. So instead of a distinction by using git or not, you could have both distros supporting both approaches if you like within their respective Dockerfile.

    To do so there would be a final two stages like this:

    FROM base-alpine AS dist-local
    COPY --chown=testssl:testssl . /home/testssl/
    
    FROM base-alpine AS dist-git
    ARG GIT_BRANCH
    ARG GIT_URL=https://github.com/testssl/testssl.sh.git
    RUN test -n "${GIT_BRANCH}"
    ADD --chown=testssl:testssl ${GIT_URL}#"${GIT_BRANCH?branch-required}" /home/testssl

    The last stage would be the default (since I'm talking about Dockerfile.git I've made it dist-git, but dist-local would usually make more sense to prefer as default). The other can be opt-in via CLI option like --target dist-local.


    Alpine 60MB => 28MB

    The current Alpine image (Dockerfile.git) is 60MB:

    • It can be reduced down to 36MB with a local COPY for parity to openSUSE Leap (Dockerfile).
    • With ADD / COPY you can also remove the git package to remove another 8MB from each build.

    The current 60MB image actually has two binaries from git (6.5MB) that have since been removed (note: you should really consider squash merges of PRs btw, commit history is messy to navigate):

    And as noted the .git/ dir was slightly larger in the current GHCR Alpine image published than when I build it locally now. Hence approx 9MB delta to disregard (effectively down to 51MB-ish). Removing .git/ (6.7MB) and the git package (8MB) is 14MB-ish (the layer size measurements from dive inspection aren't that precise below MB there is some rounding going on). That'd bring us down to around the 38MB measurement dive shows (presumably closer to 37.5MB).

    The apk update adds 2MB, only because it's run without --no-cache (kinda redundant to not store the repo updates though), apk add --no-cache implies --update (aka apk update). So that can be dropped too.

    Lack of .dockerignore difference (7-8MB)

    That leaves the remaining weight difference from the git clone itself vs the benefit of build context excludes via .dockerignore:

    Removing the excess from a git clone

    Eventually ADD will stabilize --exclude which could presumably filter that extra weight out.

    For now you could use git clone in RUN as the image currently does, but rm the two OpenSSL bins, followed by removing the git package, all within the same RUN if the verbosity is worth it:

    FROM base-alpine AS dist-git
    ARG GIT_BRANCH
    ARG GIT_URL=https://github.com/testssl/testssl.sh.git
    USER root
    RUN <<HEREDOC
      apk add --no-cache git
      git clone --depth 1 --branch "${GIT_BRANCH?branch-required}" "${GIT_URL}" /home/testssl
      cd /home/testssl
      rm -rf .git/ bin/openssl.Darwin.* bin/openssl.FreeBSD.*
      apk del git
    HEREDOC
    USER testssl

    Notes:

    • Since USER testssl was set in an earlier stage, it needs to switch back to root to add the git package (to keep it out of dist-local weight).
    • cd /home/testssl is only needed if it's not already the PWD from an earlier WORKDIR.
    • The error message trigger from ${GIT_BRANCH?branch-required} is a little worse in UX for RUN vs with ADD.
    Error message with `RUN`

    Actual error is in the middle of all that output.

     => ERROR [dist-git 1/1] RUN <<HEREDOC (apk add --no-cache git...)                                                                                                                                                                                     1.4s
    ------
     > [dist-git 1/1] RUN <<HEREDOC (apk add --no-cache git...):
    0.423 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
    0.530 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
    1.002 (1/4) Installing libexpat (2.7.0-r0)
    1.012 (2/4) Installing pcre2 (10.43-r0)
    1.025 (3/4) Installing git (2.45.3-r0)
    1.128 (4/4) Installing git-init-template (2.45.3-r0)
    1.132 Executing busybox-1.36.1-r29.trigger
    1.145 OK: 27 MiB in 50 packages
    1.273 /bin/sh: GIT_BRANCH: branch-required
    ------
    Dockerfile.git:28
    --------------------
      28 | >>> RUN <<HEREDOC
      29 | >>>   apk add --no-cache git
      30 | >>>   git clone --depth 1 --branch "${GIT_BRANCH?branch-required}" "${GIT_URL}" /home/testssl
      31 | >>>   cd /home/testssl
      32 | >>>   rm -rf .git/ bin/openssl.Darwin.* bin/openssl.FreeBSD.*
      33 | >>>   apk del git
      34 | >>> HEREDOC
      35 |     USER testssl
    --------------------
    ERROR: failed to solve: process "/bin/sh -c   apk add --no-cache git\n  git clone --depth 1 --branch \"${GIT_BRANCH?branch-required}\" \"${GIT_URL}\" /home/testssl\n  cd /home/testssl\n  rm -rf .git/ bin/openssl.Darwin.* bin/openssl.FreeBSD.*\n  apk del git\n" did not complete successfully: exit code: 2
    
    Error message with `ADD`
    Dockerfile.git:39
    --------------------
      37 |     ARG GIT_BRANCH
      38 |     ARG GIT_URL=https://github.com/testssl/testssl.sh.git
      39 | >>> ADD --chown=testssl:testssl ${GIT_URL}#"${GIT_BRANCH?branch-required}" /home/testssl
      40 |
    --------------------
    ERROR: failed to solve: failed to process "${GIT_URL}#\"${GIT_BRANCH?branch-required}\"": GIT_BRANCH: branch-required
    
    Error message with `RUN` + `test -n`

    The current check is done in RUN on the ENV existing, not too friendly UX:

     => ERROR [base-alpine 2/2] RUN test -n "${BUILD_VERSION}"     && apk update     && apk add --no-cache bash procps drill coreutils libidn curl socat openssl xxd git     && git clone --depth 1 --branch ${BUILD_VERSION} https://github.com/testssl/  0.2s
    ------
     > [base-alpine 2/2] RUN test -n "${BUILD_VERSION}"     && apk update     && apk add --no-cache bash procps drill coreutils libidn curl socat openssl xxd git     && git clone --depth 1 --branch ${BUILD_VERSION} https://github.com/testssl/testssl.sh.git /home/testssl     && addgroup testssl     && adduser -G testssl -g "testssl user" -s /bin/bash -D testssl     && ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh     && mkdir -m 755 -p /home/testssl/etc /home/testssl/bin:
    ------
    Dockerfile.git:7
    --------------------
       6 |
       7 | >>> RUN test -n "${BUILD_VERSION}" \
       8 | >>>     && apk update \
       9 | >>>     && apk add --no-cache bash procps drill coreutils libidn curl socat openssl xxd git \
      10 | >>>     && git clone --depth 1 --branch ${BUILD_VERSION} $URL /home/testssl \
      11 | >>>     && addgroup testssl \
      12 | >>>     && adduser -G testssl -g "testssl user" -s /bin/bash -D testssl \
      13 | >>>     && ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh \
      14 | >>>     && mkdir -m 755 -p /home/testssl/etc /home/testssl/bin
      15 |
    --------------------
    ERROR: failed to solve: process "/bin/sh -c test -n \"${BUILD_VERSION}\"     && apk update     && apk add --no-cache bash procps drill coreutils libidn curl socat openssl xxd git     && git clone --depth 1 --branch ${BUILD_VERSION} $URL /home/testssl     && addgroup testssl     && adduser -G testssl -g \"testssl user\" -s /bin/bash -D testssl     && ln -s /home/testssl/testssl.sh /usr/local/bin/testssl.sh     && mkdir -m 755 -p /home/testssl/etc /home/testssl/bin" did not complete successfully: exit code: 1
    

    On the topic of better error support, this can be helpful:

    SHELL ["/bin/ash", "-eux", "-o", "pipefail", "-c"]

    The -u would trigger a failure when an ENV/variable is referenced that is unset like:

    /bin/ash: BUILD_VERSION: parameter not set
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions