Configuration Patterns for Keyless Builds Using Docker Buildx Bake SSH Forwarding

Explains the implementation procedures and troubleshooting for Buildx Bake SSH forwarding to resolve SSH Permission Denied errors during Docker builds and securely pull private repositories without leaving private keys in container images.

In infrastructure automation and containerization workflows, it is common to pull private Git repositories or non-public packages (e.g., npm, Python wheels) within an organization during build time. Conventionally, operational practices involved using COPY or ADD instructions inside a Dockerfile to place SSH private keys directly into the container.

However, even if the key file is deleted in a subsequent build step, the private key remains permanently in the layer history of intermediate images, creating a risk of credential leakage via docker history or layer extraction tools. To bypass these security constraints while resolving Permission denied (publickey) errors, this document outlines configuration methods using the SSH forwarding feature in Docker Buildx Bake.

SSH Forwarding Mechanism

The Docker Buildx Bake SSH forwarding mechanism temporarily mounts the ssh-agent socket on the host machine only to specific RUN steps during build execution. This completes the cryptographic handshake via the socket without copying the key file itself to the filesystem.

[ Host SSH Agent ] ---> ( UNIX Domain Socket ) ---> [ Build Container Mount ]
                                                        |
                                              [ Git Authentication ]
                                                        |
                                            [ Socket Closed on Exit ]

Because the socket mount is unmounted as soon as the build step completes, no SSH key data remains in the final generated image layers.

Basic Configuration Structure

To enable SSH forwarding, configure both docker-bake.hcl and Dockerfile to work together correctly.

1. docker-bake.hcl

Define the ssh parameter for the target build and specify the default socket binding.

target "app" {
  context = "."
  ssh     = ["default"]
}

2. Dockerfile

Explicitly append the –mount=type=ssh option to the RUN instruction requiring SSH connection.

FROM alpine:3.19

RUN apk add --no-cache git openssh-client

# Mount SSH socket to clone the private repository
RUN --mount=type=ssh \
    git clone [email protected]:company/private-repository.git /app

Execution Steps in a Local Environment

🛠️ When executing builds in a host environment, ssh-agent must be running and the target key must be added.

# 1. Start SSH Agent
eval $(ssh-agent -s)

# 2. Add private key
ssh-add ~/.ssh/id_rsa

# 3. Verify registered keys
ssh-add -l

# 4. Run Bake command
docker buildx bake app

Application to Package Manager Dependencies

This approach applies not only to git clone, but also to the installation process of private dependency packages retrieved via SSH.

FROM node:20-slim

RUN apt-get update && apt-get install -y --no-install-recommends git openssh-client \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /usr/src/app

# Install package from private repository via npm
RUN --mount=type=ssh \
    npm install git+ssh://[email protected]:company/private-pkg.git

Integration with CI/CD Pipelines

In automated CI/CD environments, keys are not placed directly in the repository; instead, they are dynamically injected into ssh-agent from pipeline secret variables.

GitHub Actions Integration Example

name: Build Container Image

on:
  push:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Setup SSH Keys
        uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Build with Buildx Bake
        run: |
          docker buildx bake app

GitLab CI Integration Example

build_job:
  stage: build
  image: docker:24.0.7
  services:
    - docker:24.0.7-dind
  before_script:
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '' | ssh-add -
  script:
    - docker buildx bake app

Troubleshooting

⚠️ Key errors encountered in practice, along with their causes and resolution workflows, are summarized below.

Error SymptomPrimary CauseVerification Command / Resolution
Permission denied (publickey)Key not registered in host ssh-agent, or improper public key configuration on the Git service sideCheck key existence with ssh-add -l, then execute ssh-add ~/.ssh/id_rsa
SSH_AUTH_SOCK-related errorAgent process is not running on the hostVerify if echo $SSH_AUTH_SOCK outputs a value, then re-execute eval $(ssh-agent -s)
git: command not found or ssh: command not foundgit or openssh-client packages are not installed in the container imageAdd packages using apt or apk during the base image preparation phase in the Dockerfile

System Verification Protocol Logs

Example terminal log output demonstrating operational verification during build execution and proving that no key data is contained in the final image.

$ ssh-add -l
2048 SHA256:abc123xyz890... /home/user/.ssh/id_rsa (RSA)

$ docker buildx bake app
[+] Building 4.2s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                              0.0s
 => => transferring dockerfile: 210B                                              0.0s
 => [internal] load .dockerignore                                                 0.0s
 => => transferring context: 2B                                                   0.0s
 => [internal] load metadata for docker.io/library/alpine:3.19                    1.1s
 => [1/3] FROM docker.io/library/alpine:3.19@sha256:c5b54...                     0.0s
 => [2/3] RUN apk add --no-cache git openssh-client                               1.8s
 => [3/3] RUN --mount=type=ssh git clone [email protected]:company/private-repo.git  1.2s
 => exporting to image                                                            0.1s
 => => exporting layers                                                           0.1s
 => => writing image sha256:8f431a...                                             0.0s

$ docker run --rm -it app:latest ls -la /root/.ssh
ls: /root/.ssh: No such file or directory

Operational Notes

🛠️ 1. Apply the Principle of Least Privilege: For SSH keys injected into CI/CD environments, configure Deploy Keys granted read-only access to specific repositories rather than personal private keys.

⚠️ 2. Standardize Protocols: Always use the SSH format (git@…) instead of the HTTP(S) format (https://…) for repository specifications in Dockerfiles.

💡 3. Agent Cleanup: After completing work in local development host environments, adopting the habit of purging private key memory from the Agent using ssh-add -D is recommended.

Built with Hugo
Theme Stack designed by Jimmy
Privacy Policy Disclaimer Contact