mirror of
https://github.com/apache/airflow.git
synced 2026-11-04 15:19:48 +00:00
* Refresh the hardened base image mirror every six hours Docker rebuilds the hardened images in place and moves the dhi.io apt repository on at the same time. With a weekly mirror, the base images Airflow builds on lag the repository by up to a week, and any apt install whose dependencies were rebuilt in between fails: build-essential currently cannot be installed because the mirrored base has an older libbz2 than the bzip2 the repository offers (docker-hardened-images/catalog#689). Mirroring copies manifests between registries without pulling layers, so running it several times a day is cheap. Generated-by: Claude Opus 5 * Pin the hardened base images to digests the CI has passed on Building on the mirror's moving tags made CI non-reproducible: any refresh of the mirror changed the image every build started from, without a change in the repository and without the full CI having run on it. A CI run could pass or fail depending only on when it ran, and rebuilding the same commit later gave a different image. The images now build on a digest, and only the upgrade check moves it, in a PR that must pass the full CI before the new base is used. The mirror refreshes every six hours and triggers that upgrade check when it moves a tag past its pin, so the pins follow Docker's rebuilds - and the dhi.io apt repository moving with them - within hours rather than drift behind it. Every pinned and mirrored digest keeps a tag of its own, so a pinned image never becomes untagged. The hardened images keep stock Debian's apt sources beside dhi.io, and apt took whichever offered the higher version, mixing Debian builds into an image whose dependencies were built by Docker. dhi.io is now preferred whenever it publishes the package. Generated-by: Claude Opus 5 * Let dhi.io's package versions win over the image's own Docker renumbered its builds ("1.0.8-6+dhi2" became "1.0.8-6+deb13u0+dhi2", which sorts lower) and its apt repository moved to the new numbers before its images did. Every package depending on a renumbered build - bzip2, libffi-dev, libgpg-error-dev among them - is then uninstallable, because apt will not replace the image's higher-numbered copy with the repository's lower-numbered one, and the CI image build fails (docker-hardened-images/catalog#689). Every package in the image comes from dhi.io, so letting its repository decide their versions is safe, and it keeps the build working through such a renumbering. Generated-by: Claude Opus 5 * Move the base images to Docker's current Python patch releases Docker stops rebuilding a Python patch release once it publishes the next, so the 3.11.17, 3.12.15 and 3.13.16 images still carry the old package numbering that the dhi.io apt repository has moved past, and installing packages on them fails (docker-hardened-images/catalog#689). Docker fixed it in the images of the current patch releases. Generated with the Python part of the upgrade check, which also pins the new images to the digests the mirror serves for them. Generated-by: Claude Opus 5 * Compare the registry host exactly in the mirror test Generated-by: Claude Opus 5 * Lower the dhi.io apt pin below 1000 so dependencies can resolve At priority 1001 the Package:* dhi.io preference forces dhi.io's build even as a downgrade, so apt keeps dhi.io's libpq5 17.11 and postgresql-client-18 (which needs libpq5 >= 18.6 from PGDG) cannot be installed. Below 1000 the preference still favours dhi.io for the packages it publishes but lets apt take a newer version from another repository to satisfy a dependency. The forced "downgrade" the >1000 priority handled no longer arises now the base image is pinned to a digest CI has passed on. * Rank the PostgreSQL apt repository above the dhi.io preference install_postgres adds the PGDG repository, but the hardened image's Package:* dhi.io preference outranks Debian's default, so apt kept dhi.io's libpq and postgresql-client-18 (which needs a newer libpq5 than dhi.io carries) failed with held broken packages. Pinning the PostgreSQL origin above the dhi.io preference lets apt take libpq from PGDG to satisfy the client. * Use US spelling favors to pass the docs spellcheck The docker-stack docs spellcheck rejects the British spelling favours in build.rst; use the US spelling there and in the matching install-script comments. --------- Co-authored-by: Rahul Vats <43964496+vatsrahul1001@users.noreply.github.com>
1159 lines
65 KiB
ReStructuredText
1159 lines
65 KiB
ReStructuredText
.. Licensed to the Apache Software Foundation (ASF) under one
|
|
or more contributor license agreements. See the NOTICE file
|
|
distributed with this work for additional information
|
|
regarding copyright ownership. The ASF licenses this file
|
|
to you under the Apache License, Version 2.0 (the
|
|
"License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
|
|
.. http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
.. Unless required by applicable law or agreed to in writing,
|
|
software distributed under the License is distributed on an
|
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
KIND, either express or implied. See the License for the
|
|
specific language governing permissions and limitations
|
|
under the License.
|
|
|
|
.. _build:build_image:
|
|
|
|
Building the image
|
|
==================
|
|
|
|
Before you dive-deeply in the way how the Airflow Image is built, let us first explain why you might need
|
|
to build the custom container image and we show a few typical ways you can do it.
|
|
|
|
Quick start scenarios of image extending
|
|
----------------------------------------
|
|
|
|
The most common scenarios where you want to build your own image are adding a new ``apt`` package,
|
|
adding a new ``PyPI`` dependency (either individually or via requirements.txt) and embedding dags
|
|
into the image.
|
|
|
|
Example Dockerfiles for those scenarios are below, and you can read further
|
|
for more complex cases which might involve either extending or customizing the image. You will find
|
|
more information about more complex scenarios below, but if your goal is to quickly extend the Airflow
|
|
image with new provider, package, etc. then here is a quick start for you.
|
|
|
|
Adding new ``apt`` package
|
|
..........................
|
|
|
|
The following example adds ``vim`` to the Airflow image. When adding packages via ``apt`` you should
|
|
switch to the ``root`` user when running the ``apt`` commands, but do not forget to switch back to the
|
|
``airflow`` user after installation is complete.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-apt-packages/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
Adding new ``PyPI`` packages individually
|
|
.........................................
|
|
|
|
The following example adds ``lxml`` python package from PyPI to the image. When adding packages via
|
|
``pip`` you need to use the ``airflow`` user rather than ``root``. Attempts to install ``pip`` packages
|
|
as ``root`` will fail with an appropriate error message.
|
|
|
|
.. note::
|
|
In the example below, we also add apache-airflow package to be installed - in the very same version
|
|
that the image version you used it from. This is not strictly necessary, but it is a good practice
|
|
to always install the same version of apache-airflow as the one you are using. This way you can
|
|
be sure that the version you are using is the same as the one you are extending. In some cases where
|
|
your new packages have conflicting dependencies, ``pip`` might decide to downgrade or upgrade
|
|
apache-airflow for you, so adding it explicitly is a good practice - this way if you have conflicting
|
|
requirements, you will get an error message with conflict information, rather than a surprise
|
|
downgrade or upgrade of airflow. If you upgrade Airflow base image, you should also update the version
|
|
to match the new version of airflow.
|
|
|
|
.. note::
|
|
Creating custom images means that you need to maintain also a level of automation as you need to re-create the images
|
|
when either the packages you want to install or Airflow is upgraded. Please do not forget about keeping these scripts.
|
|
Also keep in mind, that in cases when you run pure Python tasks, you can use the
|
|
`Python Virtualenv functions <https://airflow.apache.org/docs/apache-airflow/stable/howto/operator/python.html#pythonvirtualenvoperator>`_
|
|
which will dynamically source and install python dependencies during runtime. With Airflow 2.8.0 Virtualenvs can also be cached.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-pypi-packages/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
Adding packages from requirements.txt
|
|
.....................................
|
|
|
|
The following example adds few python packages from ``requirements.txt`` from PyPI to the image.
|
|
Note that similarly when adding individual packages, you need to use the ``airflow`` user rather than
|
|
``root``. Attempts to install ``pip`` packages as ``root`` will fail with an appropriate error message.
|
|
|
|
.. note::
|
|
In the example below, we also add apache-airflow package to be installed - in the very same version
|
|
that the image version you used it from. This is not strictly necessary, but it is a good practice
|
|
to always install the same version of apache-airflow as the one you are using. This way you can
|
|
be sure that the version you are using is the same as the one you are extending. In some cases where
|
|
your new packages have conflicting dependencies, ``pip`` might decide to downgrade or upgrade
|
|
apache-airflow for you, so adding it explicitly is a good practice - this way if you have conflicting
|
|
requirements, you will get an error message with conflict information, rather than a surprise
|
|
downgrade or upgrade of airflow. If you upgrade Airflow base image, you should also update the version
|
|
to match the new version of airflow.
|
|
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-requirement-packages/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-requirement-packages/requirements.txt
|
|
:language: text
|
|
|
|
|
|
Embedding dags
|
|
..............
|
|
|
|
The following example adds ``test_dag.py`` to your image in the ``/opt/airflow/dags`` folder.
|
|
|
|
.. exampleinclude:: docker-examples/extending/embedding-dags/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
.. exampleinclude:: docker-examples/extending/embedding-dags/test_dag.py
|
|
:language: Python
|
|
:start-after: [START dag]
|
|
:end-before: [END dag]
|
|
|
|
Add Airflow configuration with environment variables
|
|
....................................................
|
|
|
|
The following example adds Airflow configuration to the image. ``airflow.cfg`` file in
|
|
``$AIRFLOW_HOME`` directory contains Airflow's configuration. You can set options with environment variables for those Airflow's configuration by using this format:
|
|
:envvar:`AIRFLOW__{SECTION}__{KEY}` (note the double underscores).
|
|
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-airflow-configuration/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
Extending vs. customizing the image
|
|
-----------------------------------
|
|
|
|
You might want to know very quickly whether you need to extend or customize the existing image
|
|
for Apache Airflow. This chapter gives you a short answer to those questions.
|
|
|
|
Here is the comparison of the two approaches:
|
|
|
|
+----------------------------------------------------+-----------+-------------+
|
|
| | Extending | Customizing |
|
|
+====================================================+===========+=============+
|
|
| Uses familiar 'FROM' pattern of image building | Yes | No |
|
|
+----------------------------------------------------+-----------+-------------+
|
|
| Requires only basic knowledge about images | Yes | No |
|
|
+----------------------------------------------------+-----------+-------------+
|
|
| Builds quickly | Yes | No |
|
|
+----------------------------------------------------+-----------+-------------+
|
|
| Produces image heavily optimized for size | No | Yes |
|
|
+----------------------------------------------------+-----------+-------------+
|
|
| Can build from custom Airflow sources (forks) | No | Yes |
|
|
+----------------------------------------------------+-----------+-------------+
|
|
| Can build on air-gapped system | No | Yes |
|
|
+----------------------------------------------------+-----------+-------------+
|
|
|
|
TL;DR; If you have a need to build custom image, it is easier to start with "Extending". However, if your
|
|
dependencies require compilation steps or when your require to build the image from security vetted
|
|
packages, switching to "Customizing" the image provides much more optimized images. For example,
|
|
if we compare equivalent images built by "Extending" and "Customization", they end up being
|
|
1.1GB and 874MB respectively - a 20% improvement in size for the Customized image.
|
|
|
|
.. note::
|
|
|
|
You can also combine both - customizing & extending the image in one. You can build your
|
|
optimized base image first using ``customization`` method (for example by your admin team) with all
|
|
the heavy compilation required dependencies and you can publish it in your registry and let others
|
|
``extend`` your image using ``FROM`` and add their own lightweight dependencies. This reflects well
|
|
the split where typically "Casual" users will Extend the image and "Power-users" will customize it.
|
|
|
|
Airflow Summit 2020's `Production Docker Image <https://youtu.be/wDr3Y7q2XoI>`_ talk provides more
|
|
details about the context, architecture and customization/extension methods for the Production Image.
|
|
|
|
Why customizing the image ?
|
|
---------------------------
|
|
|
|
The Apache Airflow community, releases Docker Images which are ``reference images`` for Apache Airflow.
|
|
However, Airflow has more than 90 community managed providers (installable via extras) and some of the
|
|
default extras/providers installed are not used by everyone, sometimes others extras/providers
|
|
are needed, sometimes (very often actually) you need to add your own custom dependencies,
|
|
packages or even custom providers.
|
|
|
|
In Kubernetes and Docker terms this means that you need another image with your specific requirements.
|
|
This is why you should learn how to build your own Docker (or more properly Container) image.
|
|
You might be tempted to use the ``reference image`` and dynamically install the new packages while
|
|
starting your containers, but this is a bad idea for multiple reasons - starting from fragility of the build
|
|
and ending with the extra time needed to install those packages - which has to happen every time every
|
|
container starts. The only viable way to deal with new dependencies and requirements in production is to
|
|
build and use your own image. You should only use installing dependencies dynamically in case of
|
|
"hobbyist" and "quick start" scenarios when you want to iterate quickly to try things out and later
|
|
replace it with your own images.
|
|
|
|
Building images primer
|
|
----------------------
|
|
|
|
.. note::
|
|
The ``Dockerfile`` does not strictly follow the `SemVer <https://semver.org/>`_ approach of
|
|
Apache Airflow when it comes to features and backwards compatibility. While Airflow code strictly
|
|
follows it, the ``Dockerfile`` is really a way to conveniently package Airflow using standard container
|
|
approach, occasionally there are some changes in the building process or in the entrypoint of the image
|
|
that require slight adaptation. Details of changes and adaptation needed can be found in the
|
|
:doc:`Changelog <changelog>`.
|
|
|
|
There are several most-typical scenarios that you will encounter and here is a quick recipe on how to achieve
|
|
your goal quickly. In order to understand details you can read further, but for the simple cases using
|
|
typical tools here are the simple examples.
|
|
|
|
In the simplest case building your image consists of those steps:
|
|
|
|
1) Create your own ``Dockerfile`` (name it ``Dockerfile``) where you add:
|
|
|
|
* information what your image should be based on (for example ``FROM: apache/airflow:|airflow-version|-python3.11``
|
|
|
|
* additional steps that should be executed in your image (typically in the form of ``RUN <command>``)
|
|
|
|
2) Build your image. This can be done with ``docker`` CLI tools and examples below assume ``docker`` is used.
|
|
There are other tools like ``kaniko`` or ``podman`` that allow you to build the image, but ``docker`` is
|
|
so far the most popular and developer-friendly tool out there. Typical way of building the image looks
|
|
like follows (``my-image:0.0.1`` is the custom tag of your image containing version).
|
|
In case you use some kind of registry where you will be using the image from, it is usually named
|
|
in the form of ``registry/image-name``. The name of the image has to be configured for the deployment
|
|
method your image will be deployed. This can be set for example as image name in the
|
|
:doc:`apache-airflow:howto/docker-compose/index` or in the :doc:`helm-chart:index`.
|
|
|
|
.. code-block:: shell
|
|
|
|
docker build . -f Dockerfile --pull --tag my-image:0.0.1
|
|
|
|
3) [Optional] Test the image. Airflow contains tool that allows you to test the image. This step, however,
|
|
requires locally checked out or extracted Airflow sources. If you happen to have the sources you can
|
|
test the image by running this command (in Airflow root folder). The output will tell you if the image
|
|
is "good-to-go".
|
|
|
|
.. code-block:: shell
|
|
|
|
./scripts/ci/tools/verify_docker_image.sh PROD my-image:0.0.1
|
|
|
|
4) Once you build the image locally you have usually several options to make them available for your deployment:
|
|
|
|
* For ``docker-compose`` deployment, if you've already built your image, and want to continue
|
|
building the image manually when needed with ``docker build``, you can edit the
|
|
docker-compose.yaml and replace the "apache/airflow:<version>" image with the
|
|
image you've just built ``my-image:0.0.1`` - it will be used from your local Docker
|
|
Engine cache. You can also simply set ``AIRFLOW_IMAGE_NAME`` variable to
|
|
point to your image and ``docker-compose`` will use it automatically without having
|
|
to modify the file.
|
|
|
|
* Also for ``docker-compose`` deployment, you can delegate image building to the docker-compose.
|
|
To do that - open your ``docker-compose.yaml`` file and search for the phrase "In order to add custom dependencies".
|
|
Follow these instructions of commenting the "image" line and uncommenting the "build" line.
|
|
This is a standard docker-compose feature and you can read about it in
|
|
`Docker Compose build reference <https://docs.docker.com/compose/reference/build/>`_.
|
|
Run ``docker-compose build`` to build the images. Similarly as in the previous case, the
|
|
image is stored in Docker engine cache and Docker Compose will use it from there.
|
|
The ``docker-compose build`` command uses the same ``docker build`` command that
|
|
you can run manually under-the-hood.
|
|
|
|
* For some - development targeted - Kubernetes deployments you can load the images directly to
|
|
Kubernetes clusters. Clusters such as ``kind`` or ``minikube`` have dedicated ``load`` method to load the
|
|
images to the cluster.
|
|
|
|
* Last but not least - you can push your image to a remote registry which is the most common way
|
|
of storing and exposing the images, and it is most portable way of publishing the image. Both
|
|
Docker-Compose and Kubernetes can make use of images exposed via registries.
|
|
|
|
|
|
Extending the image
|
|
-------------------
|
|
|
|
Extending the image is easiest if you just need to add some dependencies that do not require
|
|
compiling. The compilation framework of Linux (so called ``build-essential``) is pretty big, and
|
|
for the production images, size is really important factor to optimize for, so our Production Image
|
|
does not contain ``build-essential``. If you need a compiler like gcc or g++ or make/cmake etc. - those
|
|
are not found in the image and it is recommended that you follow the "customize" route instead.
|
|
|
|
How to extend the image - it is something you are most likely familiar with - simply
|
|
build a new image using Dockerfile's ``FROM`` directive and add whatever you need. Then you can add your
|
|
Debian dependencies with ``apt`` or PyPI dependencies with ``pip install`` or any other stuff you need.
|
|
|
|
Base images
|
|
...........
|
|
|
|
There are two types of images you can extend your image from:
|
|
|
|
1) Regular Airflow image that contains the most common extras and providers, and all supported backend
|
|
database clients for AMD64 platform and Postgres for ARM64 platform.
|
|
|
|
2) Slim Airflow image, which is a minimal image, contains all supported backends database clients installed
|
|
for AMD64 platform and Postgres for ARM64 platform, but contains no extras or providers, except
|
|
the 4 default providers.
|
|
|
|
.. note:: Database clients and database providers in slim images
|
|
Slim images come with database clients preinstalled for your convenience, however the default
|
|
providers included do not include any database provider. You will still need to manually install
|
|
any database provider you need
|
|
|
|
.. note:: Differences of slim image vs. regular image.
|
|
|
|
The slim image is small comparing to regular image (~500 MB vs ~1.1GB) and you might need to add a
|
|
lot more packages and providers in order to make it useful for your case (but if you use only a
|
|
small subset of providers, it might be a good starting point for you).
|
|
|
|
The slim images might have dependencies in different versions than those used when providers are
|
|
preinstalled, simply because core Airflow might have less limits on the versions on its own.
|
|
When you install some providers they might require downgrading some dependencies if the providers
|
|
require different limits for the same dependencies.
|
|
|
|
Naming conventions for the images:
|
|
|
|
+----------------+--------------------------+---------------------------------+--------------------------------------+
|
|
| Image | Python | Standard image | Slim image |
|
|
+================+==========================+=================================+======================================+
|
|
| Latest default | 3.13 | apache/airflow:latest | apache/airflow:slim-latest |
|
|
+----------------+--------------------------+---------------------------------+--------------------------------------+
|
|
| Default | 3.13 | apache/airflow:X.Y.Z | apache/airflow:slim-X.Y.Z |
|
|
+----------------+--------------------------+---------------------------------+--------------------------------------+
|
|
| Latest | 3.11,3.12,3.13,3.14 | apache/airflow:latest-pythonN.M | apache/airflow:slim-latest-pythonN.M |
|
|
+----------------+--------------------------+---------------------------------+--------------------------------------+
|
|
| Specific | 3.11,3.12,3.13,3.14 | apache/airflow:X.Y.Z-pythonN.M | apache/airflow:slim-X.Y.Z-pythonN.M |
|
|
+----------------+--------------------------+---------------------------------+--------------------------------------+
|
|
|
|
* The "latest" image is always the latest released stable version available.
|
|
|
|
.. spelling:word-list::
|
|
|
|
pythonN
|
|
|
|
Important notes for the base images
|
|
-----------------------------------
|
|
|
|
You should be aware, about a few things
|
|
|
|
* The production image of Airflow uses "airflow" user, so if you want to add some of the tools
|
|
as ``root`` user, you need to switch to it with ``USER`` directive of the Dockerfile and switch back to
|
|
``airflow`` user when you are done. Also you should remember about following the
|
|
`best practices of Dockerfiles <https://docs.docker.com/develop/develop-images/dockerfile_best-practices/>`_
|
|
to make sure your image is lean and small.
|
|
|
|
* You can use regular ``pip install`` commands (and as of Dockerfile coming in Airflow 2.9 also
|
|
``uv pip install`` - experimental) to install PyPI packages. Regular ``install`` commands should be used,
|
|
however you should remember to add ``apache-airflow==${AIRFLOW_VERSION}`` to the command to avoid
|
|
accidentally upgrading or downgrading the version of Apache Airflow. Depending on the scenario you might
|
|
also use constraints file. As of Dockerfile available in Airflow 2.9.0, the constraints file used to
|
|
build the image is available in ``${HOME}/constraints.txt.``
|
|
|
|
* The PyPI dependencies in Apache Airflow are installed in the ``~/.local`` virtualenv, of the "airflow" user,
|
|
so PIP packages are installed to ``~/.local`` folder as if the ``--user`` flag was specified when running
|
|
PIP. This has the effect that when you create a virtualenv with ``--system-site-packages`` flag, the
|
|
virtualenv created will automatically have all the same packages installed as local Airflow installation.
|
|
Note also that using ``--no-cache-dir`` in ``pip`` or ``--no-cache`` in ``uv`` is a good idea that can
|
|
help to make your image smaller.
|
|
|
|
* If your apt, or PyPI dependencies require some of the ``build-essential`` or other packages that need
|
|
to compile your python dependencies, then your best choice is to follow the "Customize the image" route,
|
|
because you can build a highly-optimized (for size) image this way. However, it requires you to use
|
|
the Dockerfile that is released as part of Apache Airflow sources (also available at
|
|
`Dockerfile <https://github.com/apache/airflow/blob/main/Dockerfile>`_).
|
|
|
|
* You can also embed your dags in the image by simply adding them with COPY directive of Airflow.
|
|
The dags in production image are in ``/opt/airflow/dags`` folder.
|
|
|
|
* You can build your image without any need for Airflow sources. It is enough that you place the
|
|
``Dockerfile`` and any files that are referred to (such as Dag files) in a separate directory and run
|
|
a command ``docker build . --pull --tag my-image:my-tag`` (where ``my-image`` is the name you want to name it
|
|
and ``my-tag`` is the tag you want to tag the image with.
|
|
|
|
* If your way of extending image requires to create writable directories, you MUST remember about adding
|
|
``umask 0002`` step in your RUN command. This is necessary in order to accommodate our approach for
|
|
running the image with an arbitrary user. Such user will always run with ``GID=0`` -
|
|
the entrypoint will prevent non-root GIDs. You can read more about it in
|
|
:ref:`arbitrary docker user <arbitrary-docker-user>` documentation for the entrypoint. The
|
|
``umask 0002`` is set as default when you enter the image, so any directories you create by default
|
|
in runtime, will have ``GID=0`` and will be group-writable.
|
|
|
|
Examples of image extending
|
|
---------------------------
|
|
|
|
Example of setting own Airflow providers
|
|
........................................
|
|
|
|
The :ref:`Airflow Providers <providers:community-maintained-providers>` are released independently of core
|
|
Airflow and sometimes you might want to upgrade specific providers only to fix some problems or
|
|
use features available in that provider version. Here is an example of how you can do it
|
|
|
|
.. exampleinclude:: docker-examples/extending/custom-providers/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
Example of adding Airflow Provider package and ``apt`` package
|
|
..............................................................
|
|
|
|
The following example adds ``apache-spark`` airflow-providers which requires both ``java`` and
|
|
python package from PyPI.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-providers/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
Example of adding ``apt`` package
|
|
.................................
|
|
|
|
The following example adds ``vim`` to the Airflow image.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-apt-packages/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
Example of adding ``PyPI`` package
|
|
..................................
|
|
|
|
The following example adds ``lxml`` python package from PyPI to the image.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-pypi-packages/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
Example of adding ``PyPI`` package with constraints
|
|
...................................................
|
|
|
|
The following example adds ``lxml`` python package from PyPI to the image with constraints that were
|
|
used to install airflow. This allows you to use the version of packages that you know were tested with the
|
|
given version of Airflow. You can also use it if you do not want to use potentially newer versions
|
|
that were released after the version of Airflow you are using.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-pypi-packages-constraints/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
Example of adding ``PyPI`` package with uv
|
|
..........................................
|
|
|
|
The following example adds ``lxml`` python package from PyPI to the image using ``uv``. This is an
|
|
experimental feature as ``uv`` is a very fast but also very new tool in the Python ecosystem.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-pypi-packages-uv/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
Example of adding packages from requirements.txt
|
|
................................................
|
|
|
|
The following example adds few python packages from ``requirements.txt`` from PyPI to the image.
|
|
Note that similarly when adding individual packages, you need to use the ``airflow`` user rather than
|
|
``root``. Attempts to install ``pip`` packages as ``root`` will fail with an appropriate error message.
|
|
|
|
.. note::
|
|
In the example below, we also add apache-airflow package to be installed - in the very same version
|
|
that the image version you used it from. This is not strictly necessary, but it is a good practice
|
|
to always install the same version of apache-airflow as the one you are using. This way you can
|
|
be sure that the version you are using is the same as the one you are extending. In some cases where
|
|
your new packages have conflicting dependencies, ``pip`` might decide to downgrade or upgrade
|
|
apache-airflow for you, so adding it explicitly is a good practice - this way if you have conflicting
|
|
requirements, you will get an error message with conflict information, rather than a surprise
|
|
downgrade or upgrade of airflow. If you upgrade Airflow base image, you should also update the version
|
|
to match the new version of airflow.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-requirement-packages/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-requirement-packages/requirements.txt
|
|
:language: text
|
|
|
|
|
|
Example of adding a Python venv for ``ExternalPythonOperator``
|
|
..............................................................
|
|
|
|
The following example creates a separate Python virtualenv inside the image and installs packages
|
|
from ``requirements.txt`` into it, leaving Airflow's own Python environment untouched. This is the
|
|
typical setup for the
|
|
:ref:`ExternalPythonOperator <apache-airflow-providers-standard:howto/operator:externalpythonoperator>`
|
|
(or the ``@task.external_python`` decorator), which executes tasks in a pre-existing, immutable
|
|
Python environment. Use ``/opt/airflow/venv/bin/python`` as the operator's ``python`` argument
|
|
to run tasks in this venv.
|
|
|
|
The venv is created without ``--system-site-packages``, so the packages installed inside it
|
|
are isolated from Airflow's own dependencies.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-python-venv/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-python-venv/requirements.txt
|
|
:language: text
|
|
|
|
Example when writable directory is needed
|
|
.........................................
|
|
|
|
The following example adds a new directory that is supposed to be writable for any arbitrary user
|
|
running the container.
|
|
|
|
.. exampleinclude:: docker-examples/extending/writable-directory/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
Example when you add packages requiring compilation
|
|
...................................................
|
|
|
|
The following example adds ``mpi4py`` package which requires both ``build-essential`` and ``mpi compiler``.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-build-essential-extend/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
The size of this image is ~ 1.1 GB when build. As you will see further, you can achieve 20% reduction in
|
|
size of the image in case you use "Customizing" rather than "Extending" the image.
|
|
|
|
Example when you want to embed dags
|
|
...................................
|
|
|
|
The following example adds ``test_dag.py`` to your image in the ``/opt/airflow/dags`` folder.
|
|
|
|
.. exampleinclude:: docker-examples/extending/embedding-dags/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
|
|
.. exampleinclude:: docker-examples/extending/embedding-dags/test_dag.py
|
|
:language: Python
|
|
:start-after: [START dag]
|
|
:end-before: [END dag]
|
|
|
|
Example of changing Airflow configuration using environment variables
|
|
.....................................................................
|
|
|
|
The following example adds Airflow configuration changes to the Airflow image.
|
|
|
|
.. exampleinclude:: docker-examples/extending/add-airflow-configuration/Dockerfile
|
|
:language: Dockerfile
|
|
:start-after: [START Dockerfile]
|
|
:end-before: [END Dockerfile]
|
|
|
|
Customizing the image
|
|
---------------------
|
|
|
|
.. warning::
|
|
|
|
In Dockerfiles released in Airflow 2.8.0, images are based on ``Debian Bookworm`` images as base images.
|
|
|
|
.. note::
|
|
You can usually use the latest ``Dockerfile`` released by Airflow to build previous Airflow versions.
|
|
Note, however, that there are slight changes in the Dockerfile and entrypoint scripts that can make it
|
|
behave slightly differently, depending which Dockerfile version you used. Details of what has changed
|
|
in each of the released versions of Docker image can be found in the :doc:`Changelog <changelog>`.
|
|
|
|
Prerequisites for building customized docker image:
|
|
|
|
* You need to enable `Buildkit <https://docs.docker.com/develop/develop-images/build_enhancements/>`_ to
|
|
build the image. This can be done by setting ``DOCKER_BUILDKIT=1`` as an environment variable
|
|
or by installing `the buildx plugin <https://docs.docker.com/buildx/working-with-buildx/>`_
|
|
and running ``docker buildx build`` command. Docker Desktop has ``Buildkit`` enabled by default.
|
|
|
|
* You need to have a new Docker installed to handle ``1.4`` syntax of the Dockerfile.
|
|
Docker version ``23.0.0`` and above are known to work.
|
|
|
|
|
|
Before attempting to customize the image, you need to download flexible and customizable ``Dockerfile``.
|
|
You can extract the officially released version of the Dockerfile from the
|
|
`released sources <https://airflow.apache.org/docs/apache-airflow/stable/installation/installing-from-sources.html>`_.
|
|
You can also conveniently download the latest released version
|
|
`from GitHub <https://raw.githubusercontent.com/apache/airflow/|airflow-version|/Dockerfile>`_. You can save it
|
|
in any directory - there is no need for any other files to be present there. If you wish to use your own
|
|
files (for example custom configuration of ``pip`` or your own ``requirements`` or custom dependencies,
|
|
you need to use ``DOCKER_CONTEXT_FILES`` build arg and place the files in the directory pointed at by
|
|
the arg (see :ref:`Using docker context files <using-docker-context-files>` for details).
|
|
|
|
Customizing the image is an optimized way of adding your own dependencies to the image - better
|
|
suited to prepare highly optimized (for size) production images, especially when you have dependencies
|
|
that require to be compiled before installing (such as ``mpi4py``).
|
|
|
|
It also allows more sophisticated usages, needed by "Power-users" - for example using forked version
|
|
of Airflow, or building the images from security-vetted sources.
|
|
|
|
The big advantage of this method is that it produces optimized image even if you need some compile-time
|
|
dependencies that are not needed in the final image.
|
|
|
|
The disadvantage it that building the image takes longer and it requires you to use
|
|
the Dockerfile that is released as part of Apache Airflow sources.
|
|
|
|
The disadvantage is that the pattern of building Docker images with ``--build-arg`` is less familiar
|
|
to developers of such images. However, it is quite well-known to "power-users". That's why the
|
|
customizing flow is better suited for those users who have more familiarity and have more custom
|
|
requirements.
|
|
|
|
The image also usually builds much longer than the equivalent "Extended" image because instead of
|
|
extending the layers that are already coming from the base image, it rebuilds the layers needed
|
|
to add extra dependencies needed at early stages of image building.
|
|
|
|
When customizing the image you can choose a number of options how you install Airflow:
|
|
|
|
* From the PyPI releases (default)
|
|
* From the custom installation sources - using additional/replacing the original apt or PyPI repositories
|
|
* From local sources. This is used mostly during development.
|
|
* From tag or branch, or specific commit from a GitHub Airflow repository (or fork). This is particularly
|
|
useful when you build image for a custom version of Airflow that you keep in your fork and you do not
|
|
want to release the custom Airflow version to PyPI.
|
|
* From locally stored binary packages for Airflow, Airflow Providers and other dependencies. This is
|
|
particularly useful if you want to build Airflow in a highly-secure environment where all such packages
|
|
must be vetted by your security team and stored in your private artifact registry. This also
|
|
allows to build Airflow image in an air-gaped environment.
|
|
* Side note. Building ``Airflow`` in an ``air-gaped`` environment sounds pretty funny, doesn't it?
|
|
|
|
You can also add a range of customizations while building the image:
|
|
|
|
* base python image you use for Airflow
|
|
* version of Airflow to install
|
|
* extras to install for Airflow (or even removing some default extras)
|
|
* additional apt/python dependencies to use while building Airflow (DEV dependencies)
|
|
* add ``requirements.txt`` file to ``docker-context-files`` directory to add extra requirements
|
|
* additional apt/python dependencies to install for runtime version of Airflow (RUNTIME dependencies)
|
|
* additional commands and variables to set if needed during building or preparing Airflow runtime
|
|
* choosing constraint file to use when installing Airflow
|
|
|
|
Additional explanation is needed for the last point. Airflow uses constraints to make sure
|
|
that it can be predictably installed, even if some new versions of Airflow dependencies are
|
|
released (or even dependencies of our dependencies!). The docker image and accompanying scripts
|
|
usually determine automatically the right versions of constraints to be used based on the Airflow
|
|
version installed and Python version. For example 2.0.2 version of Airflow installed from PyPI
|
|
uses constraints from ``constraints-2.0.2`` tag). However, in some cases - when installing Airflow from
|
|
GitHub for example - you have to manually specify the version of constraints used, otherwise
|
|
it will default to the latest version of the constraints which might not be compatible with the
|
|
version of Airflow you use.
|
|
|
|
You can also download any version of Airflow constraints and adapt it with your own set of
|
|
constraints and manually set your own versions of dependencies in your own constraints and use the version
|
|
of constraints that you manually prepared.
|
|
|
|
You can read more about constraints in :doc:`apache-airflow:installation/installing-from-pypi`
|
|
|
|
Note that if you place ``requirements.txt`` in the ``docker-context-files`` folder, it will be
|
|
used to install all requirements declared there. It is recommended that the file
|
|
contains specified version of dependencies to add with ``==`` version specifier, to achieve
|
|
stable set of requirements, independent if someone releases a newer version. However, you have
|
|
to make sure to update those requirements and rebuild the images to account for latest security fixes.
|
|
|
|
.. _using-docker-context-files:
|
|
|
|
Using docker-context-files
|
|
--------------------------
|
|
|
|
When customizing the image, you can optionally make Airflow install custom binaries or provide custom
|
|
configuration for your pip in ``docker-context-files``. In order to enable it, you need to add
|
|
``--build-arg DOCKER_CONTEXT_FILES=docker-context-files`` build arg when you build the image.
|
|
You can pass any subdirectory of your docker context, it will always be mapped to ``/docker-context-files``
|
|
during the build.
|
|
|
|
You can use ``docker-context-files`` for the following purposes:
|
|
|
|
* you can place ``requirements.txt`` and add any ``pip`` packages you want to install in the
|
|
``docker-context-file`` folder. Those requirements will be automatically installed during the build.
|
|
|
|
.. note::
|
|
In the example below, we also add apache-airflow package to be installed - in the very same version
|
|
that the image version you used it from. This is not strictly necessary, but it is a good practice
|
|
to always install the same version of apache-airflow as the one you are using. This way you can
|
|
be sure that the version you are using is the same as the one you are extending. In some cases where
|
|
your new packages have conflicting dependencies, ``pip`` might decide to downgrade or upgrade
|
|
apache-airflow for you, so adding it explicitly is a good practice - this way if you have conflicting
|
|
requirements, you will get an error message with conflict information, rather than a surprise
|
|
downgrade or upgrade of airflow. If you upgrade Airflow base image, you should also update the version
|
|
to match the new version of airflow.
|
|
|
|
|
|
.. exampleinclude:: docker-examples/customizing/own-requirements.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
* you can place ``pip.conf`` (and legacy ``.piprc``) in the ``docker-context-files`` folder and they
|
|
will be used for all ``pip`` commands (for example you can configure your own sources
|
|
or authentication mechanisms)
|
|
|
|
.. exampleinclude:: docker-examples/customizing/custom-pip.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
|
|
* you can place ``.whl`` packages that you downloaded and install them with
|
|
``INSTALL_DISTRIBUTIONS_FROM_CONTEXT`` set to ``true`` . It's useful if you build the image in
|
|
restricted security environments (see: :ref:`image-build-secure-environments` for details):
|
|
|
|
.. exampleinclude:: docker-examples/restricted/restricted_environments.sh
|
|
:language: bash
|
|
:start-after: [START download]
|
|
:end-before: [END download]
|
|
|
|
.. note::
|
|
You can also pass ``--build-arg DOCKER_CONTEXT_FILES=.`` if you want to place your ``requirements.txt``
|
|
in the main directory without creating a dedicated folder. However, it is a good practice to keep any files
|
|
that you copy to the image context in a sub-folder. This makes it easier to separate things that
|
|
are used on the host from those that are passed in Docker context. Of course, by default when you run
|
|
``docker build .`` the whole folder is available as "Docker build context" and sent to the docker
|
|
engine, but the ``DOCKER_CONTEXT_FILES`` are always copied to the ``build`` segment of the image so
|
|
copying all your local folder might unnecessarily increase time needed to build the image and your
|
|
cache will be invalidated every time any of the files in your local folder change.
|
|
|
|
.. warning::
|
|
BREAKING CHANGE! As of Airflow 2.3.0 you need to specify additional flag:
|
|
``--build-arg DOCKER_CONTEXT_Files=docker-context-files`` in order to use the files placed
|
|
in ``docker-context-files``. Previously that switch was not needed. Unfortunately this change is needed
|
|
in order to enable ``Dockerfile`` as standalone Dockerfile without any extra files. As of Airflow 2.3.0
|
|
the ``Dockerfile`` that is released with Airflow does not need any extra folders or files and can
|
|
be copied and used from any folder. Previously you needed to copy Airflow sources together with the
|
|
Dockerfile as some scripts were needed to make it work. With Airflow 2.3.0, we are using ``Buildkit``
|
|
features that enable us to make the ``Dockerfile`` a completely standalone file that can be used "as-is".
|
|
|
|
Examples of image customizing
|
|
-----------------------------
|
|
|
|
.. _image-build-pypi:
|
|
|
|
|
|
Building from PyPI packages
|
|
...........................
|
|
|
|
This is the basic way of building the custom images from sources.
|
|
|
|
The following example builds the production image for the default Python version with latest PyPI-released Airflow,
|
|
with default set of Airflow extras and dependencies. The latest PyPI-released Airflow constraints are used automatically.
|
|
|
|
.. exampleinclude:: docker-examples/customizing/stable-airflow.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
The following example builds the production image for the default Python version with default extras from ``2.3.0`` Airflow
|
|
package. The ``2.3.0`` constraints are used automatically.
|
|
|
|
.. exampleinclude:: docker-examples/customizing/pypi-selected-version.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
The following example builds the production image for the default Python version with additional Airflow extras
|
|
(``mssql,hdfs``) from ``2.3.0`` PyPI package, and additional dependency (``oauth2client``).
|
|
|
|
.. exampleinclude:: docker-examples/customizing/pypi-extras-and-deps.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
|
|
The following example adds ``mpi4py`` package which requires both ``build-essential`` and ``mpi compiler``.
|
|
|
|
.. exampleinclude:: docker-examples/customizing/add-build-essential-custom.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
The above image is equivalent of the "extended" image from previous chapter but its size is only
|
|
874 MB. Comparing to 1.1 GB of the "extended image" this is about 230 MB less, so you can achieve ~20%
|
|
improvement in size of the image by using "customization" vs. extension. The saving can increase in case you
|
|
have more complex dependencies to build.
|
|
|
|
|
|
.. _image-build-optimized:
|
|
|
|
Building optimized images
|
|
.........................
|
|
|
|
The following example builds the production image for the default Python version with additional Airflow extras from
|
|
PyPI package but it includes additional apt dev and runtime dependencies.
|
|
|
|
The dev dependencies are those that require ``build-essential`` and usually need to involve recompiling
|
|
of some python dependencies so those packages might require some additional DEV dependencies to be
|
|
present during recompilation. Those packages are not needed at runtime, so we only install them for the
|
|
"build" time. They are not installed in the final image, thus producing much smaller images.
|
|
In this case pandas requires recompilation so it also needs gcc and g++ as dev APT dependencies.
|
|
The ``jre-headless`` does not require recompiling so it can be installed as the runtime APT dependency.
|
|
|
|
.. exampleinclude:: docker-examples/customizing/pypi-dev-runtime-deps.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
.. _image-build-uv:
|
|
|
|
Building prod images using UV as the package installer
|
|
......................................................
|
|
|
|
The following example builds the production image in default settings, but uses ``uv`` to build the image.
|
|
This is an experimental feature as ``uv`` is a very fast but also very new tool in the Python ecosystem.
|
|
|
|
.. exampleinclude:: docker-examples/customizing/use-uv.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
.. _image-build-mysql:
|
|
|
|
Building images with MySQL client
|
|
.................................
|
|
|
|
.. warning::
|
|
|
|
As of Airflow 3.2.0 - our images only support installation of "MariaDB" client from default Debian
|
|
repositories. If you need to use "MySQL" client from Oracle, you need to extend the images and add
|
|
your own ways of installing the client. The main reason for that is that Oracle's
|
|
``MySQL`` client installation is broken due to expiring GPG keys and every 2 years the old
|
|
packages stop installing unless you manually update the keys (but there is a period of time when the
|
|
packages are not installable at all). MariaDB client is a drop-in replacement for MySQL client
|
|
and it works perfectly well with MySQL server.
|
|
|
|
.. _image-build-custom:
|
|
|
|
Using custom installation sources
|
|
.................................
|
|
|
|
You can customize more aspects of the image - such as additional commands executed before apt dependencies
|
|
are installed, or adding extra sources to install your dependencies from. You can see all the arguments
|
|
described below but here is an example of rather complex command to customize the image
|
|
based on example in `this comment <https://github.com/apache/airflow/issues/8605#issuecomment-690065621>`_:
|
|
|
|
In case you need to use your custom PyPI package indexes, you can also customize PYPI sources used during
|
|
image build by adding a ``docker-context-files/pip.conf`` file when building the image.
|
|
This ``pip.conf`` will not be committed to the repository (it is added to ``.gitignore``) and it will not be
|
|
present in the final production image. It is added and used only in the build segment of the image.
|
|
Therefore this ``pip.conf`` file can safely contain list of package indexes you want to use,
|
|
usernames and passwords used for authentication. More details about ``pip.conf`` file can be found in the
|
|
`pip configuration <https://pip.pypa.io/en/stable/topics/configuration/>`_.
|
|
|
|
If you used the ``.piprc`` before (some older versions of ``pip`` used it for customization), you can put it
|
|
in the ``docker-context-files/.piprc`` file and it will be automatically copied to ``HOME`` directory
|
|
of the ``airflow`` user.
|
|
|
|
Note, that those customizations are only available in the ``build`` segment of the Airflow image and they
|
|
are not present in the ``final`` image. If you wish to extend the final image and add custom ``.piprc`` and
|
|
``pip.conf``, you should add them in your own Dockerfile used to extend the Airflow image.
|
|
|
|
Such customizations are independent of the way how Airflow is installed.
|
|
|
|
.. note::
|
|
Similar results could be achieved by modifying the Dockerfile manually (see below) and injecting the
|
|
commands needed, but by specifying the customizations via build-args, you avoid the need of
|
|
synchronizing the changes from future Airflow Dockerfiles. Those customizations should work with the
|
|
future version of Airflow's official ``Dockerfile`` at most with minimal modifications od parameter
|
|
names (if any), so using the build command for your customizations makes your custom image more
|
|
future-proof.
|
|
|
|
The following - rather complex - example shows capabilities of:
|
|
|
|
* Adding Airflow extras (slack, odbc)
|
|
* Adding PyPI dependencies (``azure-storage-blob, oauth2client, beautifulsoup4, dateparser, rocketchat_API,typeform``)
|
|
* Adding custom environment variables while installing ``apt`` dependencies - both DEV and RUNTIME
|
|
(``ACCEPT_EULA=Y'``)
|
|
* Adding custom curl command for adding keys and configuring additional apt sources needed to install
|
|
``apt`` dependencies (both DEV and RUNTIME)
|
|
* Adding custom ``apt`` dependencies, both DEV (``msodbcsql17 unixodbc-dev g++) and runtime msodbcsql17 unixodbc git procps vim``)
|
|
|
|
.. exampleinclude:: docker-examples/customizing/custom-sources.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
.. _image-build-secure-environments:
|
|
|
|
Build images in security restricted environments
|
|
................................................
|
|
|
|
You can also make sure your image is only built using local constraint file and locally downloaded
|
|
wheel files. This is often useful in Enterprise environments where the binary files are verified and
|
|
vetted by the security teams. It is also the most complex way of building the image. You should be an
|
|
expert of building and using Dockerfiles in order to use it and have to have specific needs of security if
|
|
you want to follow that route.
|
|
|
|
.. _image-hardened-base:
|
|
|
|
Properties of the hardened base image
|
|
.....................................
|
|
|
|
Since Airflow 3.4.0 the images are built on the Debian 13 "trixie" ``-dev`` variant of the
|
|
`Docker Hardened Image <https://dhi.io>`_ for Python (``dhi.io/python``, mirrored to
|
|
``ghcr.io/apache/airflow/base/python``) instead of compiling Python on ``debian:bookworm-slim``. Docker
|
|
publishes the definitions these images are built from in the
|
|
`docker-hardened-images/catalog <https://github.com/docker-hardened-images/catalog>`_ repository
|
|
(``package/python`` for the Python build, ``image/python`` for the image). The differences from the
|
|
previous images that can matter when you extend or run the image are:
|
|
|
|
* **Python is patched.** Docker applies its own patches on top of the upstream CPython release
|
|
(``package/python/patch`` in the catalog). One of them makes ``pkgutil.get_data()`` reject resource
|
|
paths that are absolute or contain ``..`` (CVE-2026-3479) - upstream CPython only documents that
|
|
restriction and does not enforce it. Libraries that load bundled data through ``../`` paths fail with
|
|
``ValueError: resource must be a relative path with no parent directory components``; for example
|
|
``moto`` before 5.2.2.
|
|
* **Threads get a 1 MiB stack.** The hardened Python is compiled with
|
|
``-DTHREAD_STACK_SIZE=0x100000``, while the previous images used the ``glibc`` default (the 8 MiB
|
|
``ulimit -s``). On Python 3.12 and 3.13 the interpreter's C recursion guard is a fixed depth that
|
|
assumes the larger stack, so deeply nested data - for example a JSON body parsed in a web server worker
|
|
thread - can crash the process instead of raising ``RecursionError``. The image restores the 8 MiB
|
|
default for threads started by Python with a ``airflow-thread-stack-size.pth`` file in the base
|
|
Python's ``site-packages``; delete that file if you need the smaller stacks.
|
|
* **Python is built without profile-guided optimization.** The previous images were built with
|
|
``--enable-optimizations`` and ``--with-lto``; the hardened Python uses only ``--with-lto``, so CPU-bound Python code
|
|
can run measurably slower.
|
|
* **Python lives under** ``/usr``. The Debian 13 hardened images install Python as Debian packages
|
|
(``python-3.13``, ``libpython-3.13``, ...), so it is ``/usr/bin/python3`` with the standard library in
|
|
``/usr/lib/python3.13`` (the Debian 12 ones used ``/opt/python``). ``/usr/python`` and the
|
|
``/usr/local/bin`` links point to it, so the paths used before keep working. The standard library ships without ``.pyc`` files, so the build
|
|
compiles it into the image.
|
|
* **The OS is minimal.** The base ships no compiler, ``curl``, ``wget``, ``git``, ``gzip``, ``which`` or
|
|
``ldconfig``, no ``dash`` (``/bin/sh`` is ``bash``) and a stripped ``/etc`` - for example without
|
|
``/etc/shells`` or the PAM ``common-*`` files. The build restores what Airflow and the Debian packages
|
|
it installs need; a custom image that relied on something else being present has to install it.
|
|
* **Packages also come from Docker's Debian repository.** Next to Debian's own repositories, the Debian 13
|
|
images configure ``http://dhi.io/deb/debian/main`` (``/etc/apt/sources.list.d/dhi.sources``, signed with
|
|
``/usr/share/keyrings/dhi-deb-main.gpg``), which serves Docker's rebuilds of Debian packages. Their
|
|
versions carry a ``+dhi<N>`` suffix, so apt prefers them over Debian's builds - in the image and in
|
|
images extending it. The repository can be used anonymously: unlike pulling the ``dhi.io`` images,
|
|
installing packages from it needs no ``docker login`` or any other credentials.
|
|
* **Installing apt packages can pull in a second Python.** The Debian 13 images register their own Python
|
|
with ``dpkg`` (``python-3.12``, ``libpython-3.12``, ...), and Docker's package repository serves the
|
|
other Python versions as packages too. A Debian package that depends on ``python3`` - directly or, as
|
|
``libenchant-2-dev`` does, through its build tools - therefore makes apt install another hardened Python
|
|
version (for example ``python-3.13`` into a Python 3.12 image), and on the Debian 12 images it pulls in
|
|
Debian's own ``python3``. The second interpreter does not replace the image's Python, but it can take
|
|
over ``/usr/bin/python3`` and its headers, and C extensions built afterwards then compile against the
|
|
wrong Python. The image build fails when this happens, but a custom image that installs extra apt
|
|
packages is not checked. Check what ``apt-get install --simulate`` would install before adding packages
|
|
that might depend on ``python3``, and prefer the runtime library packages over their ``-dev`` variants.
|
|
* **The image's Python packages are held.** On the Debian 13 images, Debian's own ``python3`` packages
|
|
conflict with the image's ``python-3.X`` packages, so a package that apt can only satisfy with them -
|
|
for example NodeSource's ``nodejs`` - would make apt remove the image's Python without asking. The
|
|
image marks its Python packages with ``apt-mark hold``, so apt reports the ``python3`` conflict and the
|
|
installation fails instead. Install that software some other way - for Node.js, copy the ``node`` binary
|
|
from the official ``node`` image - rather than releasing the hold.
|
|
* **C extensions are compiled more strictly.** The Debian 13 images come with GCC 14, which turns
|
|
incompatible pointer types, integer conversions and implicit function declarations into errors, and the
|
|
hardened Python passes ``-Werror=implicit-function-declaration`` on to every C extension it builds. Old
|
|
source distributions that compiled with warnings before - for example ``xmlsec`` 1.3.14 or ``ibm-db``
|
|
3.2.0 - fail to build. Prefer versions with wheels or newer releases; if you have to build such a
|
|
package, setting ``CFLAGS="-Wno-error=incompatible-pointer-types -Wno-error=int-conversion
|
|
-Wno-error=implicit-function-declaration"`` for the installation turns the errors back into warnings.
|
|
* **apt does not refresh the shared library cache.** The base images ship ``libc-bin`` without its
|
|
``dpkg`` trigger, so installing a shared library does not run ``ldconfig``, and anything that looks
|
|
libraries up through the cache - such as ``ctypes.util.find_library()`` - does not find it. The image
|
|
adds ``/etc/apt/apt.conf.d/99airflow-ldconfig``, which runs ``ldconfig`` after every ``dpkg`` run,
|
|
including those of images extending it.
|
|
* **Some OS files are modified.** ``/etc/os-release`` identifies the system as "Docker Hardened Images
|
|
(Debian)", and ``/etc/debian_version`` differs from the Debian package's copy, so an ``apt-get upgrade``
|
|
that touches ``base-files`` would stop at ``dpkg``'s interactive configuration-file prompt. The image
|
|
ships ``/etc/dpkg/dpkg.cfg.d/airflow-keep-conffiles`` (``force-confdef`` and ``force-confold``), so
|
|
``dpkg`` keeps the image's copy of a configuration file without asking - also in images that extend
|
|
it. A package upgrade therefore does not replace a configuration file that differs from the
|
|
package's version.
|
|
* **The tags are rebuilt in place, so the default is pinned to a digest.** Docker republishes the same tags
|
|
as CVEs are fixed, and Airflow refreshes its mirror every six hours, so building from a tag alone can pick
|
|
up a newer base at any time. The default ``BASE_IMAGE`` therefore names the digest as well, and Airflow's
|
|
regular dependency upgrade moves it - in a pull request that has to pass the full CI before the new base
|
|
is used. Pass a tag without a digest to build on the newest mirrored base instead. The mirror also copies
|
|
the newest Python patch release Docker publishes, and the same upgrade moves the pinned patch level to it,
|
|
so the images follow Docker's releases rather than python.org's.
|
|
* **Packages come from Docker's apt repository first.** The image keeps stock Debian's apt sources beside
|
|
``dhi.io``, and ``/etc/apt/preferences.d/airflow-prefer-dhi`` ranks ``dhi.io`` higher, so apt installs
|
|
Docker's build of a package whenever ``dhi.io`` publishes it and falls back to Debian's otherwise - also
|
|
in images that extend it. The priority stays below 1000, so apt still favors ``dhi.io``'s build but can
|
|
take a newer version from another repository when a dependency needs it (for example PGDG's ``libpq5``).
|
|
|
|
.. _image-legacy-flavor:
|
|
|
|
Debian trixie and the legacy image flavor
|
|
.........................................
|
|
|
|
Together with the hardened base, the default images moved from Debian 12 "bookworm" to Debian 13
|
|
"trixie". Besides the newer system libraries, this shows up in two places when you extend the image:
|
|
|
|
* Several library packages were renamed in trixie's 64-bit ``time_t`` transition - for example
|
|
``libssl3`` is ``libssl3t64`` and ``libldap-2.5-0`` is ``libldap2`` - so ``RUNTIME_APT_DEPS`` or
|
|
``ADDITIONAL_RUNTIME_APT_DEPS`` that name the bookworm packages have to be updated.
|
|
* The MySQL-compatible client comes from MariaDB 11.8, the first MariaDB LTS published for trixie. The
|
|
``mysql`` named commands are provided by ``mariadb-client-compat``.
|
|
|
|
For the 3.4.x line Airflow also publishes ``legacy`` images, built as before 3.4.0: Python downloaded
|
|
from python.org, verified with its sigstore signature and compiled from sources on ``debian:bookworm-slim``.
|
|
They are deprecated and will be removed in Airflow 3.5.0. You can build one with the
|
|
``AIRFLOW_IMAGE_FLAVOR`` build argument:
|
|
|
|
.. code-block:: bash
|
|
|
|
docker build . \
|
|
--build-arg AIRFLOW_IMAGE_FLAVOR="legacy" \
|
|
--build-arg BASE_IMAGE="debian:bookworm-slim" \
|
|
--build-arg AIRFLOW_PYTHON_VERSION="3.13" \
|
|
--tag my-image:my-tag
|
|
|
|
A ``MAJOR.MINOR`` ``AIRFLOW_PYTHON_VERSION`` builds the latest python.org release of that Python line;
|
|
pass ``MAJOR.MINOR.PATCH`` to build a specific one.
|
|
|
|
The flavor an image was built with is recorded in its ``org.apache.airflow.image.flavor`` label.
|
|
|
|
.. _image-build-fips:
|
|
|
|
Build images in FIPS-compliant environments
|
|
...........................................
|
|
|
|
Airflow images are based on the `Docker Hardened Images <https://dhi.io>`_ for Python, and Docker
|
|
publishes FIPS-validated variants of those images. Those variants are only available with a paid
|
|
Docker subscription, so they cannot be the default, but you can point the build at one with the
|
|
``BASE_IMAGE`` build argument.
|
|
|
|
.. code-block:: bash
|
|
|
|
docker build . --build-arg BASE_IMAGE="dhi.io/python:3.13.16-debian13-fips-dev" --tag my-image:my-tag
|
|
|
|
.. note::
|
|
|
|
While building on a FIPS-validated base image is necessary for FIPS compliance, it is not sufficient
|
|
to make the image fully FIPS compliant. There might be other reasons for FIPS incompatibility
|
|
(for example usage of non-FIPS compliant algorithms in the software installed in the image).
|
|
You should verify the compliance of the image yourself.
|
|
|
|
This builds below builds the production image with packages and constraints used from the local
|
|
``docker-context-files`` rather than installed from PyPI or GitHub. It also disables MySQL client
|
|
installation as it is using external installation method.
|
|
|
|
Note that as a prerequisite - you need to have downloaded wheel files. In the example below we
|
|
first download such constraint file locally and then use ``pip download`` to get the ``.whl`` files needed
|
|
but in most likely scenario, those wheel files should be copied from an internal repository of such .whl
|
|
files. Note that ``AIRFLOW_VERSION_SPECIFICATION`` is only there for reference, the Apache Airflow ``.whl`` file
|
|
in the right version is part of the ``.whl`` files downloaded.
|
|
|
|
Note that 'pip download' will only works on Linux host as some of the packages need to be compiled from
|
|
sources and you cannot install them providing ``--platform`` switch. They also need to be downloaded using
|
|
the same python version as the target image.
|
|
|
|
The ``pip download`` might happen in a separate environment. The files can be committed to a separate
|
|
binary repository and vetted/verified by the security team and used subsequently to build images
|
|
of Airflow when needed on an air-gaped system.
|
|
|
|
Example of preparing the constraint files and wheel files. Note that ``mysql`` dependency is removed
|
|
as ``mysqlclient`` is installed from Oracle's ``apt`` repository and if you want to add it, you need
|
|
to provide this library from your repository if you want to build Airflow image in an "air-gaped" system.
|
|
|
|
.. exampleinclude:: docker-examples/restricted/restricted_environments.sh
|
|
:language: bash
|
|
:start-after: [START download]
|
|
:end-before: [END download]
|
|
|
|
After this step is finished, your ``docker-context-files`` folder will contain all the packages that
|
|
are needed to install Airflow from.
|
|
|
|
Those downloaded packages and constraint file can be pre-vetted by your security team before you attempt
|
|
to install the image. You can also store those downloaded binary packages in your private artifact registry
|
|
which allows for the flow where you will download the packages on one machine, submit only new packages for
|
|
security vetting and only use the new packages when they were vetted.
|
|
|
|
On a separate (air-gaped) system, all the PyPI packages can be copied to ``docker-context-files``
|
|
where you can build the image using the packages downloaded by passing those build args:
|
|
|
|
* ``INSTALL_DISTRIBUTIONS_FROM_CONTEXT="true"`` - to use packages present in ``docker-context-files``
|
|
* ``AIRFLOW_CONSTRAINTS_LOCATION=/docker-context-files/YOUR_CONSTRAINT_FILE.txt`` - to downloaded constraint files
|
|
* (Optional) ``INSTALL_MYSQL_CLIENT="false"`` if you do not want to install ``MySQL``
|
|
client from the Oracle repositories.
|
|
* (Optional) ``INSTALL_MSSQL_CLIENT="false"`` if you do not want to install ``MsSQL``
|
|
client from the Microsoft repositories.
|
|
* (Optional) ``INSTALL_POSTGRES_CLIENT="false"`` if you do not want to install ``Postgres``
|
|
client from the Postgres repositories.
|
|
|
|
Note, that the solution we have for installing python packages from local packages, only solves the problem
|
|
of "air-gaped" python installation. The Docker image also downloads ``apt`` dependencies and ``node-modules``.
|
|
Those types of dependencies are more likely to be available in your "air-gaped" system via transparent
|
|
proxies and it should automatically reach out to your private registries. However, in the future the
|
|
solution might be applied to both of those installation steps.
|
|
|
|
You can also use techniques described in the previous chapter to make ``docker build`` use your private
|
|
apt sources or private PyPI repositories (via ``.pypirc``) available which can be security-vetted.
|
|
|
|
If you fulfill all the criteria, you can build the image on an air-gaped system by running command similar
|
|
to the below:
|
|
|
|
.. exampleinclude:: docker-examples/restricted/restricted_environments.sh
|
|
:language: bash
|
|
:start-after: [START build]
|
|
:end-before: [END build]
|
|
|
|
Modifying the Dockerfile
|
|
........................
|
|
|
|
The build arg approach is a convenience method if you do not want to manually modify the ``Dockerfile``.
|
|
Our approach is flexible enough to be able to accommodate most requirements and
|
|
customizations out-of-the-box. When you use it, you do not need to worry about adapting the image every
|
|
time a new version of Airflow is released. However, sometimes it is not enough if you have very
|
|
specific needs and want to build a very custom image. In such case you can simply modify the
|
|
``Dockerfile`` manually as you see fit and store it in your forked repository. However, you will have to
|
|
make sure to rebase your changes whenever new version of Airflow is released, because we might modify
|
|
the approach of our Dockerfile builds in the future and you might need to resolve conflicts
|
|
and rebase your changes.
|
|
|
|
There are a few things to remember when you modify the ``Dockerfile``:
|
|
|
|
* We are using the widely recommended pattern of ``.dockerignore`` where everything is ignored by default
|
|
and only the required folders are added through exclusion (!). This allows to keep docker context small
|
|
because there are many binary artifacts generated in the sources of Airflow and if they are added to
|
|
the context, the time of building the image would increase significantly. If you want to add any new
|
|
folders to be available in the image you must add them here with leading ``!``
|
|
|
|
.. code-block:: text
|
|
|
|
# Ignore everything
|
|
**
|
|
|
|
# Allow only these directories
|
|
!airflow
|
|
...
|
|
|
|
|
|
* The ``docker-context-files`` folder is automatically added to the context of the image, so if you want
|
|
to add individual files, binaries, requirement files etc you can add them there. The
|
|
``docker-context-files`` is copied to the ``/docker-context-files`` folder of the build segment of the
|
|
image, so it is not present in the final image - which makes the final image smaller in case you want
|
|
to use those files only in the ``build`` segment. You must copy any files from the directory manually,
|
|
using COPY command if you want to get the files in your final image (in the main image segment).
|
|
|
|
|
|
More details
|
|
------------
|
|
|
|
Build Args reference
|
|
....................
|
|
|
|
The detailed ``--build-arg`` reference can be found in :doc:`build-arg-ref`.
|
|
|
|
|
|
The architecture of the images
|
|
..............................
|
|
|
|
You can read more details about the images - the context, their parameters and internal structure in the
|
|
`Images documentation <https://github.com/apache/airflow/blob/main/dev/breeze/doc/ci/02_images.md>`_.
|
|
|
|
|
|
Dependencies caching
|
|
....................
|
|
|
|
To enable faster iteration when building the image locally (especially if you are testing different combination of
|
|
python packages), pip caching has been enabled. The caching id is based on four different parameters:
|
|
|
|
1. ``TARGETARCH``: Avoid sharing architecture specific cached package
|
|
2. ``DEPENDENCY_CACHE_EPOCH``: Enable invalidating cache by passing ``DEPENDENCY_CACHE_EPOCH`` as ``--build-arg``
|