id-configurator
id-configurator is a CLI tool by InterDiode that configures a device (typically a
Docker image) in an air-gapped environment: no network access is required or ever used.
It reads a single configuration (JSON or YAML), and applies it to a filesystem tree, writing only the files that need to change: root certificates, system package manager sources (APT, YUM/DNF, APK) and language-specific package manager settings (pip/uv/poetry, Maven/Gradle/Ivy/SBT/Leiningen, Composer, npm, cargo, Go modules, RubyGems, NuGet). The result can then be used to build a Docker image or otherwise deployed to the target device.
How it works
--dstpoints to the destination directory that receives every modified or created file.id-configuratornever writes anywhere else.--src(optional) points to an existing filesystem, used to detect the current configuration (existing APT sources, existing Java keystores, etc.) so it can be extended or disabled instead of blindly overwritten.- The configuration is loaded from a file (
--config), from a base64-encoded string, typically an environment variable (--config-env/$ID_CONFIGURATOR_CONFIG), or downloaded over HTTP(S) from a URL (--config-url).--config-url-cacert-contentoptionally gives the PEM content of the CA certificate to trust for that one download, for an internal endpoint signed by a private CA the running system doesn't otherwise trust yet (e.g. before thecertificatesmodule has ever run) - it defaults to the system's own trust store otherwise.--config-url-insecuredisables certificate (and hostname) verification entirely instead, overriding--config-url-cacert-content- insecure, only meant for a first bootstrap against a server whose certificate isn't trustable, or trusted, yet by any other means. - On
apply, every module inspects the configuration and the source filesystem and decides for itself whether it has something to do; only relevant modules touch the destination directory. For example, the configured Alpine repositories are only written if--srccan be identified as an Alpine filesystem (i.e. it has an/etc/apkdirectory); and a source cloning Debian's official repository is only configured if that official repository is actually present in the filesystem given via--src(viauri-to-replace, see "Replacing an existing repository" below).
Installation
As a CLI tool
# with pip
pip install id-configurator
# or with uv tool, in its own isolated environment
uv tool install id-configurator
Once installed this way, run it directly as id-configurator (rather than uv run id-configurator, which is
only for the "from source" setup below).
As a Docker image
id-configurator is also published as interdiode/id-configurator
on Docker Hub - handy since its output is typically meant to become another Docker image in the first place.
One-off use:
docker run --rm -v "$(pwd)/config.yaml:/config.yaml:ro" -v "$(pwd)/output:/dst" -v "/:/src" \
interdiode/id-configurator:latest \
id-configurator apply --config /config.yaml --dst /dst --src /src
More typically, as a builder stage of the target image's own Dockerfile - <src> here is the target image
itself, taken as of its target stage, before id-configurator's own changes are copied back on top of it:
FROM debian:trixie-slim AS target
FROM interdiode/id-configurator:latest AS configuration
COPY --from=target / /src/
COPY config.yaml /config.yaml
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
FROM target
COPY --from=configuration /dst/ /
From source (development)
uv sync
Usage
# Apply a configuration file to a destination directory
id-configurator apply --config config.yaml --dst ./output
# Take an existing filesystem into account (e.g. to extend its APT sources)
id-configurator apply --config config.yaml --src /path/to/rootfs --dst ./output
# Use a base64-encoded configuration from the environment instead of a file
export ID_CONFIGURATOR_CONFIG=$(base64 -w0 config.yaml)
id-configurator apply --dst ./output
# Download the configuration from an internal HTTPS server instead of a local file
id-configurator apply \
--config-url https://config.internal.example/device.yaml \
--config-url-cacert-content "$(cat internal-ca.pem)" \
--dst ./output
# Validate a configuration without applying it
id-configurator validate --config config.yaml
# List the available modules
id-configurator list-modules
# Same thing, via the Docker image instead of installing anything locally
docker run --rm \
-v "$(pwd)/config.yaml:/config.yaml:ro" \
-v "$(pwd)/input:/src" \
-v "$(pwd)/output:/dst" \
interdiode/id-configurator:latest \
id-configurator apply --config /config.yaml --dst /dst --src /src
The tool can also be invoked as python -m id_configurator.
Configuration format
The simplest way to generate a new configuration is InterDiode itself, through its simple interface - it also handles duplicating application mirrors onto disconnected networks.
The configuration is a JSON or YAML object. uuid, organisation and description are all optional
(identifying metadata only - nothing in the tool itself relies on any of them), as is every configurations.*
block - a module that finds nothing to do for it simply does not run. version is the configuration
format's own schema version (currently "1.0", and the default if omitted) - independent of the
id-configurator package version - so a future, incompatible format change can be told apart from this one:
version: "1.0"
uuid: <UUID, optional>
organisation: <string, optional>
description: <string, optional>
configurations:
certificates:
- name: <string>
content: <PEM>
apt:
sources:
- name: <string>
uris: [<string>]
suites: [<string>]
components: [<string>]
signed-by: <ASCII-armored GPG public key> # or signed-by-file: <path already on the target>
uri-to-replace: <fnmatch pattern, optional> # see "Replacing an existing repository" below
apk:
repositories:
- name: <string>
url: <string>
key: <RSA public key, optional>
url_to_replace: <fnmatch pattern, optional>
yum:
repositories:
- name: <string>
index-url: <string>
gpg-url: <string, optional>
index-url-to-replace: <fnmatch pattern, optional>
keys:
- name: <string>
content: <GPG key>
python:
repositories:
- name: <string>
index-url: <string>
java:
repositories:
- name: <string>
url: <string>
php:
repositories:
- name: <string>
url: <string>
npm:
repositories:
- name: <string>
url: <string>
rust:
repositories:
- name: <string>
url: <string>
go:
repositories:
- name: <string>
url: <string>
ruby:
repositories:
- name: <string>
url: <string>
nuget:
repositories:
- name: <string>
url: <string>
Replacing an existing repository
apt.sources[].uri-to-replace, apk.repositories[].url_to_replace and yum.repositories[].index-url-to-replace
are optional fnmatch patterns. When set, the configured
repository is only written if the pattern matches at least one URI/URL already present in --src's existing
configuration - and only the matching existing entries are disabled, the rest are left untouched. This lets a
configuration safely target "whatever machine this is applied to": e.g. uri-to-replace: "http://deb.debian.org/*"
replaces Debian's default mirror if (and only if) it's actually there, without disturbing any other source a
given machine might already have. Leaving the field unset never disables anything - the configured repository
is simply added alongside whatever is already present.
Modules
| Module | Applies to |
|---|---|
certificates |
Installs root certificates into the system's CA store, rebuilding the trust bundle in pure Python (Debian/Ubuntu and RHEL/Fedora trust stores). |
apt |
Adds the configured APT sources (DEB822 format); disables only the existing entries matching uri-to-replace. |
apk |
Adds the configured Alpine /etc/apk/repositories entries and their keys; disables only the existing lines matching url_to_replace. |
yum |
Adds the configured YUM/DNF .repo files and imports their GPG keys; disables only the existing sections matching index-url-to-replace. Covers DNF too - same config files. |
python |
Points pip, uv, poetry and easy_install to the configured Python package indexes. |
java |
Adds root certificates to every detected JKS/PKCS12 keystore, and configures Maven, Gradle, Ivy, SBT and Leiningen repositories. |
php |
Registers the configured repositories with Composer; sets openssl.cafile whenever any certificate is configured, so PHP's own OpenSSL calls trust it too. |
npm |
Points npm at the configured registries and its own cafile; sets NODE_EXTRA_CA_CERTS system-wide whenever any certificate is configured, since Node ignores the system CA store. |
rust |
Points cargo at the configured registries, replacing crates.io. |
go |
Points the Go toolchain at the configured module proxies (GOPROXY), replacing proxy.golang.org, and disables GOSUMDB. |
ruby |
Points RubyGems at the configured sources, replacing rubygems.org. |
nuget |
Points NuGet at the configured package sources, replacing nuget.org. |
This tool's own modular structure is meant to be extended: see CLAUDE.md for the module interface and how to add one.
Development
uv sync # install dependencies
uv run pytest # run the test suite
uv run ruff check --fix . # lint
uv run ruff format . # format
uv run pre-commit run --all-files # run all pre-commit hooks
License
CeCILL v2.1 (GPL-compatible free software license). See LICENSE. Copyright InterDiode SA.
Metadata
Release files for id-configurator 2026.10.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| id_configurator-2026.10.5.tar.gz | 50.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| id_configurator-2026.10.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 109.5 kB
Release files / id_configurator-2026.10.5.tar.gz
| Download URL | id_configurator-2026.10.5.tar.gz |
|---|---|
| Size | 50.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4fa0866588b11846305d4f6b755eb5501d0c2cd1805a9c0c4dc84cca2242e26e
|
|
BLAKE2b-256 checksum How to use checksums |
059311aa4a8da1cb782f00135f5d6d453419e26730db06b7d603145535187c1e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / id_configurator-2026.10.5-py3-none-any.whl
| Download URL | id_configurator-2026.10.5-py3-none-any.whl |
|---|---|
| Size | 59.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e03360762bf3f9c6067780e7c8abdb37a2f59d989fe69ef021ab37930709da23
|
|
BLAKE2b-256 checksum How to use checksums |
6ff9b7dd83ebd12710099e4ad4d3aa0b8231ea6e363412f6b779038430660d38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|