Skip to main content

Docker secrets - Using your SSH key during the build process

Securely access private Git repositories during your Docker build using SSH keys and Docker secrets. Includes complete Dockerfile and compose.yaml examples to prevent secrets from being stored in your final image.

· 9 min read
View raw
Christophe
Markdown, WSL and Docker lover ~ Insatiable curious.
Cet article existe aussi en français — lire la version française.

This article is over a year old but was reviewed on July 30, 2026 — the content is still accurate.

Docker secrets - Using your SSH key during the build process

⚡
TL;DR

This article shows how to git clone a private repository during a Docker build without ever baking the SSH key into the final image, using Docker's RUN --mount=type=secret BuildKit feature declared in compose.yaml and referenced in the Dockerfile. It also covers the separate case of sharing your SSH key with the running container (not the image) via a bind-mounted volume, for ongoing git pull/push work inside it.

There are plenty of articles on the Internet but I didn't find the one that allowed me, without an impressive amount of trial and error, to find the solution.

So here's another article to add to the long list: how to access a private project stored on Github when creating a Docker image. In other words, the SSH key is not stored in the image. Docker will just use your key when executing the project recovery layer (the one containing the git clone instruction) and will not keep track of the key afterwards.

My use case: I wish to build a Docker image and during the build phase, I need to grab a copy of a private repository I've put on github.com. The CI equivalent of this problem — pulling a private Docker image rather than a private repository — is covered in GitLab - Using Docker private images.

When I access the container, the project will then be available but I don't have, anywhere in my image, a copy of my SSH key so I won't be able to run a git pull f.i. since no further authentication is possible.

Preview​

You're authenticated

That's the proof the technique works: the SSH key was used to authenticate against GitHub during the build — without ever being baked into the final image.

Why it works​

  • Docker's RUN --mount=type=secret mounts the secret only for the duration of that one build layer — it never gets written to a layer that ends up in the final image.
  • Copying your SSH key into the image instead (or hardcoding a token) is a serious design error: anyone reading the Dockerfile, or starting an interactive bash session and running printenv or browsing .ssh/.git folders, will find it.
  • Even a secret deleted in a later layer can survive: tools like SecretScanner do deep layer-by-layer scanning and will still find it if it was ever written to disk in an earlier layer. The same reasoning applies to your git history: Git - pre-commit-hooks lists hooks that block a commit as soon as a credential is detected.

Which key to use​

Caution

This part is one of the most important ones. First, of course, you should already have created an SSH key (see my Github - Connect your account using SSH and start to work with git@ protocol if needed).

Then you should know which protocol you've used and that's really important. Is your key stored in a file called id_ed25519 or id_rsa or something else? Only you know.

Just run ls -alh ~/.ssh to get the list of your keys:

My own keys

I'm using two different keys as you can see: id_ed25519 or id_rsa. Files having the .pub extensions are the public keys; those without extensions are the private keys.

It's important to know which key has been used to link your Github profile.

You can just display the file to your console like f.i. cat ~/.ssh/id_ed25519.pub or cat ~/.ssh/id_rsa.pub (the public key file) and look at the end: is the mentioned email is the one you're using on Github.com? If yes, you've probably identified the key.

In my case, the key I've used for github.com is id_ed25519 so I'll use that one in the next chapter.

Creating files​

We'll need two files: compose.yaml and Dockerfile.

compose.yaml​

Here is my compose.yaml content:

services:
app:
build:
context: .
dockerfile: Dockerfile
// highlight-next-line
secrets:
// highlight-next-line
- my_ssh_key
args:
// highlight-next-line
- KEY_NAME=id_ed25519
// highlight-next-line
secrets:
// highlight-next-line
my_ssh_key:
// highlight-next-line
file: ${HOME}/.ssh/id_ed25519

This Docker Compose configuration sets up a service that builds a Docker image while securely passing an SSH key into the build process. The `secrets` section in the build defines what sensitive data the container can access during building, and the top-level `secrets` section tells Docker where to find that SSH key file on the host machine.

As you can see, we've five specific lines.

In our services -> app --> build entry, we should add the secrets and the name of one (or more) secrets.

I've also added an argument called KEY_NAME just because I need to inform Docker which key I'm using (is it id_ed25519 or id_rsa or another one).

Secrets have to be defined at the same indentation level of services and the notation is this one:

secrets:
a_secret_name:
file: existing_file

This YAML snippet defines a secret named 'a_secret_name' that reads its value from a file called 'existing_file'. In container orchestration systems like Docker Swarm or Kubernetes, this tells the system to load sensitive data from disk rather than embedding it directly in the configuration.

You can use what you want for a_secret_name; for instance, my_ssh_key.

docker compose config

You can, if you want, run docker compose config to check if your file is correct. You'll also see the full path for the used key.

Docker compose config

Dockerfile​

Time to create our second file, Dockerfile:

FROM alpine:3.19
ARG KEY_NAME="id_rsa"
USER root
RUN apk add --no-cache bash ca-certificates git openssh-client
// highlight-next-line
RUN mkdir -p -m 0700 /root/.ssh && ssh-keyscan "github.com" >> /root/.ssh/known_hosts
// highlight-next-line
RUN --mount=type=secret,id=my_ssh_key,dst=/root/.ssh/${KEY_NAME} \
// highlight-next-line
mkdir -p /app && cd /app \
// highlight-next-line
&& ssh -T git@github.com || true \
// highlight-next-line
&& git clone git@github.com:cavo789/my_private_repo.git
WORKDIR /app
# Keep the container running
ENTRYPOINT ["tail", "-f", "/dev/null"]

This Dockerfile creates a lightweight Alpine container that can clone private GitHub repositories using SSH. It sets up SSH client tools, configures GitHub's host key to prevent connection prompts, securely mounts an SSH private key at build time, tests the connection, and clones a private repository into the container.

Remember to replace cavo789/my_private_repo.git with one of your own repositories

As you can see, we are setting KEY_NAME="id_rsa" as the default value (but will be overwritten by our declaration in the yaml file) then later on, we need to create the /root/.ssh folder and we are adding github.com in the list of known hosts.

The most important thing comes then. We should use the RUN --mount=type=secret syntax to inform Docker that this layer will use a Docker secret. We need to provide the name of our secret (which was set in our yaml file and it's my_ssh_key here) then we need to define where that secret has to be stored during this layer. Our variable KEY_NAME finds its use here: our SSH key was id_ed25519 and this is the value of the KEY_NAME variable.

So, in short --mount=type=secret,id=my_ssh_key,dst=/root/.ssh/${KEY_NAME} will be translated to --mount=type=secret,id=my_ssh_key,dst=/root/.ssh/id_ed25519.

During this layer thus, our local SSH key will be saved to /root/.ssh/id_ed25519. The other lines in that RUN layer will first create a /app folder, jump in it, then run ssh -T git@github.com just for debugging (we expect to see on the screen Hi your_name! You've successfully authenticated) and finally we're git cloning our private repository using SSH.

Create the image and jump in the container​

Ok, so you've the created the two files on your hard disk.

Run docker compose --progress plain build --no-cache in your console to build the image and enable the verbose mode.

As you can see, we can confirm that our SSH key was shared during the build process — that's the "You're authenticated" output shown at the top of this article.

You'll now create the container; by running docker compose up --detach.

The container is being created

Now, just to check, we can jump inside the container by running docker compose exec app /bin/bash.

Starting an interactive console

We can verify that our key wasn't stored in the image by running ls -alh /root/.ssh/

No keys in the .ssh folder

We can check too: jumping in our project folder and running git pull will fail with the error below:

>user@machine: ~/yourproject

And this is what we expect: our key isn't part of the image so we can't connect anymore to Github.

Sharing your keys also with the container​

Ok, we've seen how to share the SSH key with Docker but only during the build phase. As we've seen above, jumping in the container and running git pull has failed; exactly what we wanted in the previous chapter.

But now, if we need to share our key also with the container so we can work on the project, update it and push changes / pull modifications, how to do?

Please edit the compose.yaml file and add these two lines:

services:
app:
build:
context: .
dockerfile: Dockerfile
secrets:
- my_ssh_key
args:
- KEY_NAME=id_ed25519
// highlight-next-line
volumes:
// highlight-next-line
- ${HOME}/.ssh:/root/.ssh
secrets:
my_ssh_key:
file: ${HOME}/.ssh/id_ed25519

This Docker Compose configuration sets up a service called 'app' that builds a Docker image using a Dockerfile in the current directory. It passes an SSH key into the build process both as a secret and by mounting the host's SSH directory into the container, allowing the application to authenticate with external services that require SSH credentials.

Then recreate the container by running docker compose up --detach. Jump in the container by running docker compose exec app /bin/bash and go in your project folder. Now, by running git pull, as you'll see, it'll work.

Why? Because your SSH keys are now part of the container as we can see by running ls -alh /root/.ssh:

Keys are now part of the container

Keys are not stored in the image even when you've shared them with the container

Just to be clear: the notion of volume we've just implemented concerns the container and not the image. In other words, SSH keys are not stored in the Docker image. You could give it to someone else (e.g. by saving the image on Docker Hub); your keys won't be there and will remain on your computer. Your image and your secrets are safe.

Then, when the user run docker compose up --detach using the compose.yaml file where there are the two lines we've just added (the ones for adding the volume); these are his keys, on his computer, not yours.

Please take note of the running user in your container

In our example here, the default user is root as we can see by jumping in the container and running whoami.

Whoami

So, when we've started git pull, it was under root. This is why we've mounted our volume like below:

services:
app:
[...]
// highlight-next-line
volumes:
// highlight-next-line
- ${HOME}/.ssh:/root/.ssh

This is a Docker Compose configuration snippet that mounts the SSH keys from your home directory into a container's root user directory. This allows the containerized application to use your SSH credentials for things like accessing git repositories or remote servers without needing to copy keys into the image.

Imagine the current user was christophe. In that case the mount should be done like this:

services:
app:
[...]
// highlight-next-line
volumes:
// highlight-next-line
- ${HOME}/.ssh:/christophe/.ssh

This Docker Compose configuration mounts the SSH credentials from your local machine into a container so that the app service can authenticate using your SSH keys. The ${HOME} variable expands to your user's home directory, making the config portable across different machines.

Conclusion​

Two different needs, two different mechanisms: a build-time secret (--mount=type=secret) that never touches the final image, for one-off operations like cloning a private repo during docker build; and a bind-mounted volume, for when the running container itself needs your key on an ongoing basis to git pull/push. Pick the first by default, and only add the second when you actually need to work from inside the container.


Was this article helpful?
Did you try the steps in this article?