How to make lightweight docker image for python app with pipenv
How about,
FROM python:3.7-alpine
WORKDIR /myapp
COPY Pipfile* ./
RUN pip install --no-cache-dir pipenv && \
pipenv install --system --deploy --clear
COPY src .
CMD ["python3", "app.py"]
- It utilises the smaller Alpine version.
- You won't have any unnecessary cache files left over using
--no-cache-dir
option forpip
and--clear
option forpipenv
. - You also deploy outside of venv.
You can also add && pip uninstall pipenv -y
after pipenv install --system --deploy --clear
in the same RUN
command to eliminate space taken by pipenv
if that extra image size bothers you.
I am using micropipenv for the job, which describes itself as
A lightweight wrapper for pip to support requirements.txt, Pipenv and Poetry lock files or converting them to pip-tools compatible output. Designed for containerized Python applications but not limited to them.
An image created from it would look like the following.
Since the alpine base image lacks a toml parser we have to use the version of micropipenv that includes the toml extras (micropipenv[toml]
instead of micropipenv
).
FROM python:3.9-alpine
WORKDIR /myapp
COPY Pipfile Pipfile.lock ./
RUN \
# Install dependencies
&& pip install --no-cache-dir micropipenv[toml] \
&& micropipenv install --deploy \
&& pip uninstall -y micropipenv[toml]
COPY src .
CMD ["python3", "app.py"]
The problem comes when you need things like ciso8601
, or some libraries, requiring build process. Build tools are not "incorporated" into the both slim
and alpine
variants, for low-size footprint.
So to install deps, you'll have to:
- Install build tools
- Deploy dependencies from Pipfile.lock system-wide
- Uninstall build tools and clean caches
And do that 3 actions inside a single RUN
layer, like following:
FROM python:3.7-slim
WORKDIR /app
# both files are explicitly required!
COPY Pipfile Pipfile.lock ./
RUN pip install pipenv && \
apt-get update && \
apt-get install -y --no-install-recommends gcc python3-dev libssl-dev && \
pipenv install --deploy --system && \
apt-get remove -y gcc python3-dev libssl-dev && \
apt-get autoremove -y && \
pip uninstall pipenv -y
COPY app ./
CMD ["python", "app.py"]
- Manipulating build system would cost you around 300MiB and some extra time
- Uninstalling pipenv would save you another 20MiB (which is 10% of resulting size).
- Separating
RUN
commands would not delete data from layers, and would result in ~500MiB image. That's docker specifics.
So that would result in perfectly working ~200MiB sized image, which is
- 5 times less than original
python:3.7
, (that is >1.0GiB) - Has no alpine incompabilities (these are typically tied to glibc replacement)
At the time, we're fine with slim
(debian buster
) build variants, preferring slim
over alpine
(for most compatibility). If you're really up to further size optimization, I'd recommend you to take a look at some excellent builds of these guys:
- Alpine Python
- 12.7MiB MariaDB