A pure Python CLI tool for downloading Docker images without Docker itself. Single-file architecture with zero runtime dependencies, designed for air-gapped environments, corporate networks with proxy requirements, and seamless image transfers between systems.
- Zero Dependencies - Uses only the Python 3.11+ standard library
- Registry v2/OCI Compatibility - Pulls from standard root-path Registry v2 endpoints
- Corporate Proxy Support - Full HTTP/HTTPS proxy support with authentication
- Multi-Architecture - Pull images for different architectures (amd64, arm64, etc.)
- Progress Tracking - Real-time download progress with terminal-safe progress bars
- Format Support - Handles Docker v2, OCI, and multi-architecture manifests
- Integrity Verification - Verifies descriptor sizes and cryptographic digests before archiving
# Download the script
curl -O https://raw.githubusercontent.com/ZacharyArthur/pythonDockerDownloader/main/docker_pull.pygit clone https://github.com/ZacharyArthur/pythonDockerDownloader.git
cd pythonDockerDownloader- Python 3.11 or later - No additional runtime dependencies required
# Pull an image
python3 docker_pull.py ubuntu:latest
# Pull with custom output name
python3 docker_pull.py nginx:alpine -o my-nginx.tar
# Load into Docker (if Docker is available)
docker load -i ubuntu_latest.tar# Pull latest image
python3 docker_pull.py ubuntu:latest
# Custom output name
python3 docker_pull.py nginx:alpine -o my-nginx.tar
# Different architecture
python3 docker_pull.py ubuntu:latest --arch arm64
# Select a specific ARM variant
python3 docker_pull.py alpine:latest --arch arm --variant v7
# Another public registry
python3 docker_pull.py icr.io/codeengine/helloworld:latest
# Private repository (username/password or a ready-made Bearer token)
printf '%s\n' "$REGISTRY_PASSWORD" | \
python3 docker_pull.py registry.example.com/team/image:tag \
--username "$REGISTRY_USER" --password-stdin
printf '%s\n' "$REGISTRY_TOKEN" | \
python3 docker_pull.py registry.example.com/team/image:tag --token-stdin
# Digest-pinned pull (does not fabricate a RepoTag)
python3 docker_pull.py registry.example.com/team/image@sha256:DIGEST
# Trusted local development registry using plain HTTP
python3 docker_pull.py localhost:5000/team/image:tag --plain-http# Simple proxy setup
python3 docker_pull.py ubuntu:latest --proxy http://proxy.company.com:8080
# With authentication
python3 docker_pull.py ubuntu:latest \
--proxy http://proxy.company.com:8080 \
--proxy-auth username:password
# Environment variables
export HTTPS_PROXY=http://proxy.company.com:8080
export NO_PROXY=localhost,127.0.0.1,.local
python3 docker_pull.py ubuntu:latest
# Corporate environment (disable SSL verification)
python3 docker_pull.py ubuntu:latest --proxy https://proxy.corp.com:8080 --insecure| Option | Description | Default |
|---|---|---|
-o, --output |
Output filename | Derived from the image name and reference |
--arch |
Target architecture | amd64 |
--variant |
Target architecture variant, such as v6 or v7 for ARM |
None |
--os |
Target OS | linux |
-t, --token |
Authentication token on the command line | None |
--token-stdin |
Read an authentication token from standard input | False |
--username |
Registry username | None |
--password |
Registry password or API key on the command line | None |
--password-stdin |
Read registry password or API key from standard input | False |
--proxy |
Proxy URL | None |
--proxy-auth |
Proxy credentials | None |
--http-proxy |
HTTP proxy URL, overriding the environment | None |
--https-proxy |
HTTPS proxy URL, overriding the environment | None |
--no-proxy |
Comma-separated hosts or host:port pairs that bypass proxies | None |
--insecure |
Disable SSL verification | False |
--plain-http |
Use HTTP for a trusted development registry | False |
--debug |
Enable debug output | False |
-v, --verbose |
Use INFO logging (the default) | False |
-q, --quiet |
Show only errors; suppress progress output | False |
--log-level |
Explicit DEBUG, INFO, WARNING, or ERROR logging | None |
Supported architectures: amd64, arm64, arm, 386, ppc64le, s390x, mips64le, riscv64. Use --variant when an image publishes multiple variants for the selected architecture. arm64/v8 also matches indexes that omit the default v8 variant.
If logging options are combined, precedence is --debug, --quiet, --log-level, then --verbose.
The downloader uses the registry hostname in a fully-qualified image name and follows standard Registry v2 Bearer or Basic authentication challenges. No vendor-specific code is required. Registry hostnames are case-insensitive, but repository paths must be lowercase.
| Registry | Verification | Image-name/authentication notes |
|---|---|---|
| Docker Hub | Live archive check | Short names such as alpine:latest, or any Docker Hub alias |
| GitHub Container Registry (GHCR) | Live archive check | ghcr.io/OWNER/IMAGE:TAG; private images use a username and PAT |
| GitLab Container Registry | Live manifest/config check | registry.gitlab.com/GROUP/PROJECT/IMAGE:TAG; private images use GitLab-supported credentials or tokens |
| Quay.io | Live archive check | quay.io/ORG/IMAGE:TAG |
| Amazon Elastic Container Registry (ECR) | ECR Public manifest/config; private protocol-reviewed | public.ecr.aws/... or ACCOUNT.dkr.ecr.REGION.amazonaws.com/... |
| Google Artifact Registry | Live public manifest/config check | LOCATION-docker.pkg.dev/PROJECT/REPOSITORY/IMAGE:TAG; credentialed pull not tested |
| Azure Container Registry (ACR) | Protocol-reviewed | NAME.azurecr.io/REPOSITORY/IMAGE:TAG; credentialed pull not tested |
| IBM Cloud Container Registry | Live public manifest/config check | REGION.icr.io/NAMESPACE/IMAGE:TAG; credentialed pull not tested |
| Oracle Cloud Infrastructure Registry (OCIR) | Protocol-reviewed | REGION.ocir.io/TENANCY/REPOSITORY:TAG; Oracle's separate public registry passed manifest/config checks |
| DigitalOcean Container Registry | Protocol-reviewed | registry.digitalocean.com/REGISTRY/IMAGE:TAG |
| Harbor | Protocol-reviewed | HOST/PROJECT/IMAGE:TAG; follows the deployment's Bearer or Basic challenge |
| CNCF Distribution (Docker Registry) | Manual local testing | HOST:PORT/IMAGE:TAG; add --plain-http only for a trusted HTTP development registry |
| Nexus Repository OSS | Protocol-reviewed | Docker connector hostname/port; no customer deployment tested |
| JFrog Artifactory | Live public manifest/config check | Docker repository hostname/subdomain method only; credentialed pull not tested |
| Red Hat Quay (self-hosted) | Protocol-reviewed | Quay hostname and repository path; no customer deployment tested |
"Live archive check" means a complete image archive was downloaded and its layer diff-IDs were independently verified. "Live manifest/config check" means this implementation retrieved an image manifest and the selected platform's configuration blob. "Protocol-reviewed" means the documented Registry v2 flow and local tests match, but no vendor deployment was exercised. No credentialed private-registry pull has been verified end to end. Registries that expose the API under a path prefix instead of at /v2/ are not supported.
The checked Nextcloud images are nextcloud:latest on Docker Hub, which passed a live manifest/config check, and ghcr.io/nextcloud-releases/all-in-one:latest on GHCR, which passed a live archive check.
The complete registry.k8s.io/pause:3.9 flow was also verified through archive creation, including its fully-qualified RepoTags entry and uncompressed layer diff-ID.
Prefer short-lived credentials with --password-stdin or --token-stdin so secrets do not appear in command history or process listings. Multi-line passwords, such as JSON service-account keys, are accepted with --password-stdin; Bearer tokens must be a single line.
# Amazon ECR
aws ecr get-login-password --region us-east-1 | \
python3 docker_pull.py ACCOUNT.dkr.ecr.us-east-1.amazonaws.com/repository:tag \
--username AWS --password-stdin
# Google Artifact Registry
gcloud auth print-access-token | \
python3 docker_pull.py us-docker.pkg.dev/PROJECT/REPOSITORY/IMAGE:TAG \
--username oauth2accesstoken --password-stdin
# Azure Container Registry
az acr login --name NAME --expose-token --query accessToken -o tsv | \
python3 docker_pull.py NAME.azurecr.io/repository:tag \
--username 00000000-0000-0000-0000-000000000000 --password-stdin
# IBM Cloud Container Registry
printf '%s\n' "$IBM_CLOUD_API_KEY" | \
python3 docker_pull.py icr.io/namespace/image:tag \
--username iamapikey --password-stdin--plain-http sends registry traffic, including credentials, without encryption. Use it only with a trusted local development registry. --insecure still uses HTTPS but disables certificate verification; use it only when you trust the endpoint and network. The two options are mutually exclusive.
- Select Registry - Uses Docker Hub for short names or the registry in a fully-qualified image name
- Authenticate - Follows the registry's Bearer or Basic authentication challenge
- Get Manifest - Downloads up to 4 MiB of manifest/index JSON and selects the requested platform and optional
--variant, normalizing registry-reportedaarch64,x86_64,x86-64,i386, andarm64/v8names;--archaccepts the canonical names listed above - Download Layers - Streams layers to disk and verifies descriptor sizes/digests
- Create Archive - Verifies uncompressed layer diff-IDs and atomically replaces the output with a complete modern Docker/Podman-compatible tar
Supported Formats: Docker Registry v2, OCI images, multi-architecture manifests
Gzip and uncompressed layers are supported. Zstandard-compressed OCI layers, nested image indexes, schema v1 manifests, and foreign/nondistributable layers fail with clear errors. The foreign-layer limitation affects Windows images whose base layers are hosted outside the registry. OCI descriptors do not currently provide a standard uncompressed-layer size, so decompression has no arbitrary size cap; pull only from registries you trust.
Archives use manifest.json, as consumed by current Docker and Podman releases. Obsolete pre-manifest Docker archive metadata is not emitted.
For restrictive corporate environments:
# Typical corporate setup
export HTTPS_PROXY=https://proxy.corp.com:8080
export NO_PROXY=localhost,.corp.com
python3 docker_pull.py --insecure ubuntu:latestTips:
- Use
--insecurefor self-signed proxy certificates - Add the selected registry to the proxy whitelist for better performance
- Use
--debugto troubleshoot connection issues
Proxy Issues:
- Verify proxy URL and credentials with
--debug - Try
--insecurefor certificate problems - Check
NO_PROXYsettings for the selected registry
Architecture Errors:
- Available platforms are listed for multi-architecture images;
--debugadds request-level detail - Some images don't support all architectures
Download Failures:
- A timeout aborts the pull without replacing the output; check the network and retry. The extra per-chunk watchdog uses POSIX
SIGALRM; Windows relies on the socket download timeout. - Check network connectivity and proxy configuration
- Single-file design - Complete functionality in
docker_pull.py - Zero runtime dependencies - Uses only the Python 3.11+ standard library
- Cross-platform compatibility - Works on Linux, macOS, and Windows; per-chunk
SIGALRMenforcement is POSIX-only - CLI contract -
docker_pull.pyis the supported interface; internal classes are not a stable library API
- Docker Registry API v2
- OCI (Open Container Initiative) images
- Multi-architecture manifests
- Private repository authentication
- PEP 8 compliant - Formatted with Ruff
- Static analysis - Ruff, Vulture, and Pyright checks
- Comprehensive logging - Configurable log levels and quiet mode
- Error handling - Graceful failure with helpful messages
# Create the Python 3.11 environment and install locked development tools
uv sync --locked --python 3.11
# Activate it (choose one)
source .venv/bin/activate
# .\.venv\Scripts\Activate.ps1 # Windows PowerShell
# Run the test suite and syntax validation
uv run pytest -q
uv run python -m py_compile docker_pull.py test_docker_pull.py# Linting, formatting, dead-code, type, and test checks
uv run ruff check .
uv run ruff format --check .
uv run vulture docker_pull.py test_docker_pull.py
uv run pyright docker_pull.py test_docker_pull.py
uv run pytest -q- Python 3.11 or later
- No external dependencies - uses only standard library
MIT License - see LICENSE file for details.
This project maintains a single-file, zero-dependency architecture for maximum portability. When contributing:
- Maintain Python 3.11+ compatibility
- Avoid external dependencies
- Follow PEP 8 style guidelines
- Include tests for new functionality
- Preserve the single-file design