Container operators isolate the most, because each task runs in its own image and can use any language. ExternalPythonOperator is the middle path: a prebuilt, immutable Python environment you can review in advance. PythonVirtualenvOperator is the most limited, since it installs packages from a package index on every run.
What each option does
The Airflow best practices page describes four ways to keep task dependencies apart from Airflow's own.
| Option | Where dependencies come from | Isolation | Security trade-off |
|---|---|---|---|
| PythonVirtualenvOperator | Installed per task run | Python packages only | Workers need access to PyPI or a private repository at run time |
| ExternalPythonOperator | A prebuilt environment | Python packages only | You cannot add packages to it, so it is easier to vet |
| DockerOperator | An image built upfront | Full, any language | Airflow needs access to a Docker engine |
| KubernetesPodOperator | An image built upfront | Full, any language | Airflow needs access to a Kubernetes cluster |
The page calls the virtualenv approach the "most limited strategy" and notes it adds CPU, network and time overhead on every task.
The review question behind each choice
A security reviewer cares about one thing: can we list what ran, ahead of time?
With the virtualenv operator, the answer is "only if you also read the requirement list inside each task and trust the index on the day it ran". Versions can drift between runs unless every entry is pinned.
With ExternalPythonOperator, the environment exists before the task starts. The docs state it requires "a pre-existing, immutable Python environment" and that you cannot add new dependencies to it. That is a limitation for developers and a feature for reviewers. The page itself says the dependencies are easier to vet with admins and security teams, and suggests developing with the virtualenv operator, then switching to the external one for production.
With container operators, the image is the unit of review. You scan it, record its digest, and promote it. The cost is that you now own image builds and a container platform.
Isolation has limits
Even the isolated options share a worker. The docs warn that tasks on the same worker can still affect each other through files they create or modify. Treat these operators as dependency isolation, not as a sandbox for untrusted code.
A simple decision guide
- Prototyping a task: use the virtualenv operator, and pin every version.
- Production Python tasks with a stable package set: build one environment per task family and use the external operator.
- Tasks needing system libraries, other languages, or strict separation: use a container operator and reference the image by digest.
- Anything that runs code from outside your team: do not rely on any of these alone. Add a real sandbox.
What to inventory
Whichever you pick, the dependency list lives in a different place. For the virtualenv operator it is inside the DAG file. For the external operator it is in the environment build script. For container operators it is in the image build. A dependency inventory that only reads the repository root will miss at least one of these locations, so point your scanner at all three.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how packages from more than one manifest appear in a single inventory.
Safeguard's SBOM and dependency scanning can build that single inventory from manifests and images, and the free scan at https://gold.safeguard.sh/scan is a quick way to see what it finds in a repository.
Quick check
Does the virtualenv operator work without internet access?
Only if workers can reach a private package repository. The Airflow docs say workers need access to PyPI or private repositories to install dependencies.
Can I add packages to an ExternalPythonOperator environment at run time?
No. The docs say you cannot add new dependencies to the pre-existing environment, which is why it is easier to review.
Sources
Last reviewed: 19 October 2026.