Area: Deployment Issues
Sub-Area: Remote Executor — Private Package Index Authentication
Issue
When running a DataHub Remote Executor and publishing custom ingestion transformer wheels to a private PyPI-compatible repository such as AWS CodeArtifact, teams need a way to authenticate pip or uv at runtime so that packages declared in a recipe's extra_pip_packages field can be installed dynamically — without baking every wheel version into the executor container image. This challenge is complicated by short-lived CodeArtifact authorization tokens and differences between persistent-host deployments (EC2) and ephemeral container deployments (AWS Fargate).
You Might Be Asking
- Can the Remote Executor install
extra_pip_packagesfrom a private PyPI index at runtime? - Where should AWS CodeArtifact token refresh happen so ingestion runs always have a valid token?
- How do I avoid rebuilding the executor image every time I publish a new wheel version?
- How do I handle CodeArtifact authentication on Fargate, where I cannot run a cron job on the host?
- Is there a startup hook in the executor to install packages like
keyringwithout maintaining a custom image?
Solution
Prerequisites and Image Constraints
-
Use a
slimorunbundledexecutor image. Variants with names containinglocked-wolfi,locked-distroless, or similar have runtime dependency resolution deliberately disabled and their index pinned to an address that does not work. Private index authentication will not function with those variants. -
Do not disable dependency resolution. Ensure the environment variable
INGESTION_DEPENDENCY_RESOLUTION_ENABLEDis not set tofalse. Its default value istrue; if it is explicitly set tofalsethe executor uses only bundled packages and skips runtime installs entirely. - Do not pin recipes to the bundled version. If a recipe is pinned to the bundled source version, the executor uses the prebuilt virtual environment and skips the runtime install step. Ensure your recipe is not forcing bundled resolution.
-
Use
UV_EXTRA_INDEX_URL, notUV_INDEX_URL, for your private repo. The executor usesuvfor package installation. SettingUV_EXTRA_INDEX_URLto your CodeArtifact repo keeps public PyPI as the primary index (so public dependencies continue to resolve) while adding CodeArtifact as an extra source for private wheels.uvalso reads the pip equivalents (PIP_INDEX_URL,PIP_EXTRA_INDEX_URL), but theUV_*variables are preferred.
Option A — Fargate: Keyring-Based Token Refresh (Recommended for Ephemeral Containers)
On Fargate there is no persistent host to run a cron job. Instead, use the keyrings.codeartifact keyring backend so that uv fetches a fresh CodeArtifact token via the task's IAM role on every install invocation. No cron job, no sidecar, and no static token in the environment is needed.
-
Install the keyring packages at executor startup using the boot-time hook. The executor reads
DATAHUB_EXECUTOR_EXTRA_PACKAGESand runsuv pip installon those packages before the worker comes up. Becausekeyringandkeyrings.codeartifactare on public PyPI, no authentication is required for this step.
There is also a companion variableDATAHUB_EXECUTOR_EXTRA_PACKAGES="keyring keyrings.codeartifact"DATAHUB_EXECUTOR_EXTRA_PACKAGES_STRICT(defaultfalse) that causes the container to fail at startup if the install errors — set it totrueif you want a hard failure instead of a silent skip.
Note: Packages installed viaDATAHUB_EXECUTOR_EXTRA_PACKAGESgo into the executor's own base environment, not into the per-run virtual environment created for each ingestion job. Do not put your custom transformer wheel here — it would not be importable at ingestion time. Keep the transformer wheel inextra_pip_packagesin the recipe, which installs into the correct per-run virtual environment. -
Add the following environment variables to your Fargate task definition. These can be added as
Type: valueentries in the task definition environment block, the same way other executor variables such asAWS_REGIONandDATAHUB_EXECUTOR_MODEare set.DATAHUB_EXECUTOR_EXTRA_PACKAGES="keyring keyrings.codeartifact" UV_KEYRING_PROVIDER=subprocess UV_EXTRA_INDEX_URL=https://aws@<domain>-<account-id>.d.codeartifact.<region>.amazonaws.com/pypi/<repo>/simple/The
UV_EXTRA_INDEX_URLvalue intentionally contains a username (aws) but no password. Whenuvencounters an index URL with a username and no password, it calls out to the keyring provider. Thekeyrings.codeartifactbackend recognizes the CodeArtifact host pattern and fetches a fresh authorization token using the ambient AWS credentials — on Fargate, those are the task IAM role credentials. This means every ingestion run gets a fresh token automatically. -
Ensure the task IAM role has all required permissions.
GetAuthorizationTokenalone is not sufficient for the full install flow. The task role needs:sts:GetServiceBearerTokencodeartifact:GetAuthorizationTokencodeartifact:GetRepositoryEndpoint-
codeartifact:ReadFromRepository— scoped to the target repository
-
Add your private wheel to the recipe's
extra_pip_packages. With the above in place, the only change needed in each recipe is declaring the package:source: type: <your-source-type> config: # ... source config ... transformers: - type: <your-transformer-type> extra_pip_packages: - your-custom-transformer-wheel==<version> -
Verify keyring availability. After deploying, exec into the container and confirm that the keyring backend is reachable:
keyring --version
Option B — EC2 Persistent Host: netrc File + Cron Refresh
If the executor runs on a persistent EC2 host, the recommended approach is to decouple the CodeArtifact token from the process environment (which is frozen at startup) and instead write the token to a ~/.netrc file that uv reads fresh on each invocation.
- Do not export the token as an environment variable directly. The executor process reads its environment once at startup. If the CodeArtifact token is set in the environment at launch it will expire during a long-running executor session and every subsequent install will fail until the process is restarted.
-
Set the index URL as an environment variable. Export
UV_EXTRA_INDEX_URLwithout a password; credentials will be supplied via~/.netrc.export UV_EXTRA_INDEX_URL="https://<domain>-<account-id>.d.codeartifact.<region>.amazonaws.com/pypi/<repo>/simple/" -
Write a
~/.netrcfile for the user account the executor process runs as, with amachineentry matching the CodeArtifact hostname:
If you prefer a non-default location, set themachine <domain>-<account-id>.d.codeartifact.<region>.amazonaws.com login aws password <CODEARTIFACT_AUTH_TOKEN>NETRCenvironment variable to the full path of the file. -
Run a cron job on the EC2 host (under the same user/role) to refresh the token every few hours (CodeArtifact tokens expire after 12 hours by default, so refreshing every 6–8 hours is safe). Because
uvreads the~/.netrcfile fresh on each install invocation, a rewrite of the file is picked up by the next ingestion run without restarting the executor process.
Example crontab entry:#!/bin/bash # Example cron script: place in crontab to run every 6 hours TOKEN=$(aws codeartifact get-authorization-token \ --domain <domain> \ --domain-owner <account-id> \ --query authorizationToken \ --output text) cat > ~/.netrc <<EOF machine <domain>-<account-id>.d.codeartifact.<region>.amazonaws.com login aws password ${TOKEN} EOF chmod 600 ~/.netrc0 */6 * * * /path/to/refresh-codeartifact-token.sh -
Add your private wheel to the recipe's
extra_pip_packagesexactly as shown in Option A, Step 4.
Option C — Fargate Fallback: Sidecar Container with Shared Volume
If installing additional packages into the executor image or using the keyring approach is not acceptable, a Fargate-compatible alternative to the cron approach is to run a sidecar container in the same task definition that fetches the CodeArtifact token and writes a ~/.netrc file to a shared volume mounted by the executor container (with NETRC pointing to the shared path). The sidecar can loop and refresh the file periodically. This is more complex to configure than Option A and is generally only recommended if the keyring approach cannot be used.
Additional Notes
- The
DATAHUB_EXECUTOR_EXTRA_PACKAGESboot-time hook installs into the executor's own base environment. It is intentionally separate from the per-run virtual environment used by ingestion jobs. Custom transformer wheels must remain in the recipe'sextra_pip_packages. - CodeArtifact authorization tokens have a default maximum lifetime of 12 hours. Plan refresh intervals accordingly.
- The
UV_KEYRING_PROVIDER=subprocessvariable instructsuvto invoke thekeyringCLI as a subprocess for credential lookup. This requireskeyringto be installed and on$PATHwithin the executor environment — verify withkeyring --version. - By default,
uvqueries the primary index (public PyPI) before the extra index. UseUV_EXTRA_INDEX_URL(notUV_INDEX_URL) for your private repo to preserve public PyPI resolution for all standard dependencies. - The
INGESTION_DEPENDENCY_RESOLUTION_ENABLEDflag defaults totrue. You do not need to set it explicitly unless you are overriding a prior explicitfalsevalue. - These approaches apply to DataHub Cloud Remote Executor deployments (customer-managed). They are not applicable to the DataHub-managed executor.
Related Documentation
- Setting Up the Remote Ingestion Executor
- Ingestion Recipe Configuration
- Remote Executor Extra Packages (Boot-time Hook)
Tags: remote-executor, aws-codeartifact, fargate, ec2, private-pypi, uv, keyring, extra-pip-packages, runtime-authentication, deployment