~/python/how-install-private-artifactory-modules-securely-not-exposing-secrets-dockerfile.md

Install Private Artifactory Modules Securely in a Python Docker Build

3 min readMay 20, 2021by Gorav Singal#python#docker#security
// TL;DR

Use Docker multi-stage builds or Docker BuildKit's --mount=type=secret to install private artifactory modules without exposing credentials in the final image layers.

Install Private Artifactory Modules Securely in a Python Docker Build

Introduction

I was having a private artifactory, and has my custom modules deployed there. I wanted to build a docker image, where I was using my private artifacts. I needed to pass credentials to build process, so that I could install those private artifacts.

I tried using ARG and ENV in Dockerfile, and it did successfully built. But, when I tried running docker image history --no-trunc <image>, I could see the credentials naked.

So, the goal is to build docker image, by passing secrets. But, not exposing it in docker image history.

I will describe two solutions:

  • Multi-stage Docker build
  • Docker buildkit

Example Which Exposes Secret in Docker Image History

FROM YOUR_IMAGE:1.0 as intermediate

WORKDIR /var/www/html/
COPY . /var/www/html/

ARG ARTIFACTORY_USER
ARG ARTIFACTORY_TOKEN

RUN virtualenv env --python python3.9 \
    && source /var/www/html/env/bin/activate \
    && python3.9 -m pip install -r /var/www/html/requirements.txt -i https://${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}@artifactory.MY.com/artifactory/api/pypi/MY/simple \
    && python3.9 -m pip install mod_wsgi -i https://${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}@artifactory.MY.com/artifactory/api/pypi/MY/simple

RUN virtualenv env --python python3.9 \
    && source /var/www/html/env/bin/activate \
    && mod_wsgi-express install-module 

COPY ./docker/httpd.conf /etc/httpd/conf/httpd.conf
COPY ./docker/sites-enabled /etc/httpd/sites-enabled

ENV CONFIG "/var/www/html"
EXPOSE 8080
USER root

CMD  ["/usr/sbin/httpd","-D","FOREGROUND"]

Now build and run docker image history --no-trunc <YOUR_IMAGE> You could see somewhere in between the step and which exposes secrets in plain text:

<missing>                                                                 13 seconds ago       RUN |2 ARTIFACTORY_USER=USER ARTIFACTORY_TOKEN=TOKEN /bin/sh -c virtualenv env --python python3.9     && source /var/www/html/env/bin/activate     && python3.9 -m pip install -r /var/www/html/requirements.txt -i https://${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}@artifactory.MY.com/artifactory/api/pypi/MY/simple     && python3.9 -m pip install mod_wsgi -i https://${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}@artifactory.MY.com/artifactory/api/pypi/MY/simple # buildkit   102MB     buildkit.dockerfile.v0
<missing>                                                                 13 seconds ago       ARG ARTIFACTORY_TOKEN      

Multi-stage Docker Build

Lets have a look at one of example I made,

FROM YOUR_IMAGE:1.0 as intermediate

WORKDIR /var/www/html/
COPY . /var/www/html/

ARG ARTIFACTORY_USER
ARG ARTIFACTORY_TOKEN

RUN virtualenv env --python python3.9 \
    && source /var/www/html/env/bin/activate \
    && python3.9 -m pip install -r /var/www/html/requirements.txt -i https://${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}@artifactory.MY.com/artifactory/api/pypi/pypi-MY-release/simple \
    && python3.9 -m pip install mod_wsgi -i https://${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}@artifactory.MY.com/artifactory/api/pypi/MY/simple

FROM YOUR_IMAGE:1.0

WORKDIR /var/www/html/
COPY --from=intermediate /var/www/html /var/www/html

RUN virtualenv env --python python3.9 \
    && source /var/www/html/env/bin/activate \
    && mod_wsgi-express install-module 

COPY ./docker/httpd.conf /etc/httpd/conf/httpd.conf
COPY ./docker/sites-enabled /etc/httpd/sites-enabled

ENV CONFIG "/var/www/html"
EXPOSE 8080
USER root

CMD  ["/usr/sbin/httpd","-D","FOREGROUND"]

Notice the two FROM keyword in above Dockerfile. The command to build this is:

docker build -t data-ws --build-arg ARTIFACTORY_USER=<USER> --build-arg ARTIFACTORY_TOKEN=<ARTIFACTORY_TOKEN> .

I’m passing two arguments to dockerfile: ARTIFACTORY_USER, and ARTIFACTORY_TOKEN. Now, if you do: docker image history --no-trunc <image>. You will not see the first stage command history. And, you are able to successfully hide your secrets from the build process.

Buildkit from Docker

In this solution, I will pass complete pip.conf file to docker build process. And, I will copy that file inside image before running pip install. Also I need to set PIP_CONFIG_FILE environment variable to set path of custom pip.conf file.

FROM YOUR_IMAGE:1.0

WORKDIR /var/www/html/
COPY . /var/www/html/data

ENV PIP_CONFIG_FILE /root/.pip.conf

RUN --mount=type=secret,id=pip.conf,dst=/root/.pip.conf virtualenv env --python python3.9 \
    && source /var/www/html/env/bin/activate \
    && python3.9 -m pip install -r /var/www/html/data/requirements.txt \
    && python3.9 -m pip install mod_wsgi \
    && mod_wsgi-express install-module 

COPY ./docker/httpd.conf /etc/httpd/conf/httpd.conf
COPY ./docker/sites-enabled /etc/httpd/sites-enabled
EXPOSE 80
USER root
CMD  ["/usr/sbin/httpd","-D","FOREGROUND"]

Build command:

docker build -f Dockerfile --secret id=pip.conf,src=/path/to/pip.conf -t MY_IMAGE .

Note the --mount=type=secret,id=pip.conf,dst=/root/.pip.conf. From the build command, I’m passing source file information in src, and I’m setting its id as id. In the Dockerfile, I’m receiving it in same id. And, copying it with dst path inside the docker image.

Note, the file used in dst will not be copied in the image produced. The magic :) It will not be present even in the intermediate layers.

Hope you enjoy the post.

$ subscribe --weekly

One deep dive a week, in your inbox

System design, AI engineering, trading systems and interview prep. Tested code, real numbers. Free, and you can unsubscribe any time.

or read it on gyanbyte.substack.com

Share

Related Posts

Dockerfile for building Python 3.9.2 and Openssl for FIPS

Dockerfile for building Python 3.9.2 and Openssl for FIPS

Introduction In previous posts, we saw how to build FIPS enabled Openssl, and…

How to Patch and Build Python 3.9.x for FIPS enabled Openssl

How to Patch and Build Python 3.9.x for FIPS enabled Openssl

Introduction In this post, we will see Python 3.9.x patch for FIPS enabled…

How to Patch and Build Python 3.7.9 for FIPS enabled Openssl

How to Patch and Build Python 3.7.9 for FIPS enabled Openssl

Introduction In this post, we will see Python 3.7.9 patch for FIPS enabled…

How to build FIPS enabled Openssl in docker

How to build FIPS enabled Openssl in docker

Introduction In this post, we will see how we can build FIPS enabled openssl in…

SQL Injection: The Complete Guide to Understanding, Preventing, and Detecting SQLi Attacks

SQL Injection: The Complete Guide to Understanding, Preventing, and Detecting SQLi Attacks

SQL injection has been on the OWASP Top 10 since the list was created in 200…

Software Security in the AI Era: How to Write Secure Code When AI Writes Code Too

Software Security in the AI Era: How to Write Secure Code When AI Writes Code Too

In 2025, 72% of professional developers used AI-assisted coding tools daily. By…

Latest Posts

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

In the previous article I argued that AI is not killing software engineering…

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

A few months back I approved a PR that was almost entirely AI-generated. It…

How to Debug AI-Generated Code: Understand First, Fix Last

How to Debug AI-Generated Code: Understand First, Fix Last

In the last article I wrote about reviewing AI code before it goes in. This one…

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

Every few weeks somebody sends me a scary headline — “AI will replace all…

The Transactional Outbox Pattern: Never Lose an Event Again

The Transactional Outbox Pattern: Never Lose an Event Again

This is one of those patterns that looks like nothing until you get burnt once…

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Here is a question I like to ask in interviews: your bank account shows a…