Tool for mirroring/downloading items from the Internet Archive. Improves the ia cli workflow
3.0K
ia-mirror is a Docker-first Internet Archive mirroring utility. It wraps the internetarchive Python package and ia CLI with resumable downloads, batch queuing, structured reports, metadata caching, bandwidth throttling, and a persistent Web UI. See what ia-mirror adds beyond internetarchive for a feature-by-feature comparison with the upstream tool.
Use reasonable concurrency, keep polite backoff enabled, and avoid unnecessary repeated metadata fetches. If you rely on Internet Archive heavily, consider donating at https://archive.org/donate/.
report.jsonThe container starts in Web UI mode by default.
| Mode | How it starts | Primary use |
|---|---|---|
| Web UI | WEB_ENABLED=true or unset | Queue jobs, manage history, run downloads through the browser; no IA_IDENTIFIER is needed at startup |
| CLI | WEB_ENABLED=false | Direct one-shot downloads or scripted runs; requires IA_IDENTIFIER or a CLI identifier argument |
Pull the latest published image:
docker pull themorgantown/ia-mirror:latest
docker run -d \
--name ia-mirror \
-v "$PWD/mirror:/downloads" \
-v "$PWD/ia-state:/data" \
-p 127.0.0.1:17865:17865 \
-e WEB_HOST=0.0.0.0 \
-e WEB_SECRET_KEY="replace-with-a-long-random-value" \
themorgantown/ia-mirror:latest
Open http://localhost:17865.
Two flags deserve explanation:
-e WEB_HOST=0.0.0.0 is required whenever you publish the port. Inside the container, the server binds to 127.0.0.1 by default as a hardening measure, and Docker's port mapping cannot reach a loopback-bound server.-p 127.0.0.1:17865:17865 keeps the UI reachable only from the machine running Docker. To reach it from other devices on your network (a NAS or homelab box, for example), use -p 17865:17865 instead.If WEB_SECRET_KEY is unset, the app generates a random secret at startup. That is safe, but sessions reset when the container restarts.
The left-hand side of each -v flag is the folder on your computer. The example above saves into ./mirror next to where you ran the command. To save into your Downloads folder instead:
-v "$HOME/Downloads/ia-mirror:/downloads"-v "$HOME/Downloads/ia-mirror:/downloads"-v "C:/Users/yourname/Downloads/ia-mirror:/downloads"If you use Docker Compose, set DOWNLOAD_DIR in a .env file instead — see Docker Compose.
The examples in this README use bash syntax and work as-is in WSL2 and Git Bash. In PowerShell, replace the trailing \ line continuations with backticks (`) or put the command on one line; $PWD works in PowerShell, but in cmd.exe use %cd% instead. Write Windows paths with forward slashes, for example C:/Users/yourname/Downloads.
docker run --rm \
-v "$PWD/mirror:/downloads" \
-e WEB_ENABLED=false \
-e IA_IDENTIFIER=The_Babe_Ruth_Collection \
-e IA_DESTDIR=/downloads \
-e IA_DRY_RUN=true \
themorgantown/ia-mirror:latest
No credentials are needed for public-item dry runs. Remove IA_DRY_RUN (or set it to 0) to actually download.
ia configure on the host, then mount ~/.config/ia:/home/app/.config/ia:ro.IA_ACCESS_KEY and IA_SECRET_KEY as environment variables or save them from the Web UI Global Settings panel.ia.ini secret and copy it into /home/app/.config/ia/ia.ini at startup.docker exec -it <container> ia whoamiThe entrypoint writes /home/app/.config/ia/ia.ini with mode 600 when credentials are supplied through environment variables.
Set IA_USER_AGENT_SUFFIX to append a custom string to the User-Agent that
internetarchive sends (requires internetarchive >= 5.7.2). The entrypoint writes it
to the [general] section of ia.ini, so it applies in both Web UI and CLI mode. The
default User-Agent, including the access key, is always still sent.
| Port | Variable | Purpose |
|---|---|---|
17865 | WEB_PORT | Web UI and API served by Gunicorn |
8080 | IA_HEALTH_PORT | Fetcher health/report server for active download jobs |
The container healthcheck targets http://localhost:17865/api/status.
| Path | Required | Purpose |
|---|---|---|
/downloads | Yes | Download destination, logs, reports, and status files |
/data | Required by default | Web UI SQLite database and persistent queue/history state |
/data is only optional if you run exclusively in CLI mode with WEB_ENABLED=false.
When IA_DESTDIR=/downloads, downloaded files land in /downloads/<identifier>/..., with logs and status files stored alongside that item directory.
docker-compose.yml is intended for local Web UI use. It starts the browser UI by default and does not require an IA_IDENTIFIER; enter item IDs or archive.org/details/... URLs in the UI.
docker compose up -dcp docker/example.env docker/live.env and add your archive.org credentials. Job settings belong in the UI, not this file — see Key Environment VariablesBy default the Web UI is published on 127.0.0.1 and only reachable from the machine running Docker. To allow access from other devices on your network, change the ports: entry in docker-compose.yml from "127.0.0.1:17865:17865" to "17865:17865".
Without configuration, files are saved to ./downloads next to docker-compose.yml. To save somewhere else, create a .env file at the project root (Compose reads it automatically):
cp .env.example .env
Then set DOWNLOAD_DIR to a full absolute path:
# macOS
DOWNLOAD_DIR=/Users/yourname/Downloads
# Windows
DOWNLOAD_DIR=C:/Users/yourname/Downloads
# Linux
DOWNLOAD_DIR=/home/yourname/Downloads
Notes:
~) is not expanded by Docker Compose — write the full path.DOWNLOAD_DIR="/Users/yourname/My Downloads".DATA_DIR works the same way for the /data volume (database and queue state)..env at the project root, not docker/live.env. Compose only reads volume paths from .env.docker compose down && docker compose up -d.You can also change this from the Web UI: the Settings panel shows the current download location and, when you type a new path, generates the exact .env line with a copy button. A container restart applies it.
Set WEB_ENABLED=false and a real IA_IDENTIFIER in docker/live.env, or pass them with docker compose run --rm -e WEB_ENABLED=false -e IA_IDENTIFIER=The_Babe_Ruth_Collection ia-mirror.
Do not use IA_IDENTIFIER=example_item; it is placeholder text. Blank or missing IA_IDENTIFIER is fine for Web UI mode, but CLI mode exits with identifier required.
If you already configured ia on your host, mount it into the service:
services:
ia-mirror:
volumes:
- ~/.config/ia:/home/app/.config/ia:ro
To switch from dry run to real downloads, set IA_DRY_RUN=0 in your compose env file, or remove the line.
The Web UI accepts one identifier or URL per line.
Examples:
baberuthstory0000ruthhttps://archive.org/details/baberuthstory0000rutharchive.org/details/baberuthstory0000ruthThe UI normalizes each line into an IA identifier, then enqueues jobs with the chosen operation and config.
Basic controls include:
/downloadsdownload, verify, sync)The Global Settings modal persists UI defaults and optional IA credentials to the SQLite database. It also shows the current host download location and helps you change it (see Choosing the download folder).
GET /api/configPOST /api/configGET /api/destinationsPOST /api/destinations/validatePOST /api/maintenance/clear-historyPOST /api/queue/addPOST /api/queue/reorderDELETE /api/queue/<id>POST /api/job/startPOST /api/job/stopPOST /api/jobs/<id>/unlockGET /api/statusGET /api/jobsGET /api/jobs/recentGET /api/jobs/<id>GET /api/jobs/<id>/logGET /api/jobs/<id>/logsGET /api/watcher/collectionsPOST /api/watcher/collectionsDELETE /api/watcher/collections/<identifier>GET /api/files/listGET /api/files/downloadGET /api/files/contentPOST /api/files/deleteThe file browser is restricted to /downloads and rejects traversal attempts.
Clients connect to / and can request status with request_status. The server emits status_update, job_update, job_progress, log_line, and queue_update.
docker run --rm \
-v "$PWD/mirror:/downloads" \
-e WEB_ENABLED=false \
-e IA_IDENTIFIER=listofearlyameri00fren \
-e IA_DESTDIR=/downloads \
-e IA_DRY_RUN=true \
themorgantown/ia-mirror:latest
docker run --rm \
-v "$HOME/.config/ia:/home/app/.config/ia:ro" \
-v "$PWD/mirror:/downloads" \
-e WEB_ENABLED=false \
-e IA_IDENTIFIER=jillem-full-archive \
-e IA_DESTDIR=/downloads \
-e IA_CONCURRENCY=6 \
-e IA_CHECKSUM=1 \
themorgantown/ia-mirror:latest
docker run --rm \
-v "$PWD/mirror:/downloads" \
-e WEB_ENABLED=false \
themorgantown/ia-mirror:latest \
The_Babe_Ruth_Collection --destdir /downloads --verify-only
Create a CSV with source and destdir columns:
source,destdir,glob,exclude,format,concurrency,verify_mode
The_Babe_Ruth_Collection,/downloads/The_Babe_Ruth_Collection,*.mp3,,mp3,10,checksum
jillem-full-archive,/downloads/jillem-full-archive,,*_thumb.jpg,,5,size
Run it with:
docker run --rm \
-v "$PWD/mirror:/downloads" \
-v "$PWD/batch_source.csv:/app/batch_source.csv:ro" \
-e WEB_ENABLED=false \
-e IA_ACCESS_KEY=AKXXX \
-e IA_SECRET_KEY=SKYYY \
themorgantown/ia-mirror:latest \
--use-batch-source --batch-source-path /app/batch_source.csv
See docker/example.env for the full template.
.env at project root)| Variable | Default | Notes |
|---|---|---|
DOWNLOAD_DIR | ./downloads | Host folder mounted at /downloads; full absolute paths only |
DATA_DIR | ./data | Host folder mounted at /data |
| Variable | Default | Notes |
|---|---|---|
WEB_ENABLED | true | Starts the Web UI unless explicitly disabled |
WEB_HOST | 127.0.0.1 | Gunicorn bind host inside the container. Set to 0.0.0.0 whenever you publish the port; docker-compose.yml does this for you |
WEB_PORT | 17865 | Web UI listen port |
WEB_DB_PATH | /data/ui.db | SQLite database path used by the entrypoint |
WEB_RUNNER | real | real or mock |
WEB_SECRET_KEY | generated at startup if unset | Set explicitly for stable sessions |
WEB_CORS_ORIGINS | unset | Optional comma-separated allowed origins for separate frontends; leave unset for same-origin Web UI use |
These apply to CLI mode only. Web UI jobs ignore them: every job carries its own
settings, chosen in the browser or posted to /api/queue/add, and defaults for new jobs
live in Settings (/api/config). Leaving job settings in docker/live.env on a Web UI
container has no effect on queued jobs — put credentials there and configure jobs in the app.
On the command line these variables supply defaults. An explicit argument always wins,
so --glob '*' overrides IA_GLOB=*.zip.
| Variable | Default | Notes |
|---|---|---|
IA_IDENTIFIER | none | Not needed for Web UI startup; required in CLI mode unless provided as a CLI arg |
IA_DESTDIR | /downloads | Root destination directory |
IA_CONCURRENCY | 4 | Parallel workers |
IA_DRY_RUN | false | Simulate downloads |
IA_VERIFY_MODE | size | exists, size, or checksum |
IA_CHECKSUM | unset | Shortcut for checksum verification |
IA_SYNC | unset | Deletes local files that no longer exist remotely |
IA_MAX_MBPS | unset | Native Python throttling |
IA_HEALTH_PORT | 8080 | Disable with 0 |
IA_USE_BATCH_SOURCE | unset | Enables CSV batch mode |
IA_BATCH_SOURCE_PATH | ./batch_source.csv | Batch CSV path |
Per item, ia-mirror writes:
/downloads/<identifier>/ia_download.log/downloads/<identifier>/report.json/downloads/<identifier>/.ia_status/<identifier>.json/downloads/<identifier>/.ia_status/lock.jsonreport.json is produced for dry runs and estimate-only runs as well as completed downloads.
internetarchiveUpstream jjjake/internetarchive provides the ia CLI and Python API for Archive.org operations. As of 5.11.1 its commands are account, configure, copy, delete, download, flag, list, metadata, move, reviews, search, simplelists, tasks, and upload. ia-mirror keeps internetarchive as its core dependency, then adds the mirroring appliance features below.
Comparisons in this table are against internetarchive 5.11.1, the version pinned in docker/requirements.txt.
| Feature in ia-mirror | Upstream internetarchive status | What this project adds |
|---|---|---|
| Persistent Web UI | Not native; upstream is CLI/Python API focused | Browser queue manager, global settings, job history, file browser, log viewer, and WebSocket progress |
| Docker-first appliance | Not native; upstream supports pip, pipx, source installs, and a standalone binary | Production container with Gunicorn Web UI, healthcheck, non-root runtime, Compose/Unraid-oriented defaults, and mounted /downloads + /data state |
| SQLite job queue and history | Not native | Durable queued/running/completed job state, reorder/delete controls, and automatic queue resume after restart |
| Per-item mirror reports | Not native | report.json, .ia_status/<identifier>.json, lock files, and status snapshots beside each downloaded item |
| Built-in parallel mirror workers | No multi-item concurrency; upstream recommends composing with tools such as GNU Parallel. Item.download(range_jobs=...) (5.10.0) parallelizes byte ranges within a single file only | -j/IA_CONCURRENCY worker pool inside the wrapper with aggregate progress and ETA |
| Collection watcher | Not native | Background service that watches collections and queues new/future items |
| Browser/API batch input | Partially covered by upstream --itemlist and --search | Paste identifiers or archive.org URLs into the UI/API and normalize them into queued jobs with shared settings |
| CSV source-to-destination batch mode | Not native for downloads | Batch CSV mode that maps each source identifier to its own destination path and wrapper settings |
| Verify-only mirror checks | Upstream -C/--checksum and --checksum-archive skip files during a download; there is no standalone verify pass | --verify-only checks existing local files without downloading, with exists, size, or checksum verification modes |
| Local sync cleanup | Not native for local mirrors | --sync removes local files that are no longer present in the remote IA item manifest |
| Estimate and cost reporting | Partially covered: upstream ia download --dry-run prints the URLs it would fetch | --estimate-only adds total size, assumed-bandwidth time estimates, and optional cost-per-GB calculations on top of a dry run |
| Bandwidth cap and aggregate speed sampling | Not native | Approximate --max-mbps throttling plus sampled aggregate transfer speed/ETA |
| Polite global backoff controls | Partially covered: upstream has -R/--retries, -t/--timeout, and honors the Retry-After header (5.6.0) | Wrapper-level exponential backoff across the whole run for HTTP 429/5xx responses, with configurable base/max/multiplier/jitter |
| Container-friendly env configuration | Upstream has config files and its own credential/env conventions | IA_* and WEB_* env-to-argument injection, --print-effective-config, and automatic ia.ini creation from IA_ACCESS_KEY/IA_SECRET_KEY |
| ZIP folder resume helper | Not native | --resumefolders skips ZIP downloads when the expected extracted folder already exists |
Two upstream behaviors worth knowing, since ia-mirror inherits them:
internetarchive 5.9.0, downloads send cnt=0 and do not count toward
archive.org view counts. ia-mirror does not expose upstream's --count-views
opt-in, so mirroring never inflates an item's view count.ia download --range (5.10.0) and ia download --stdout are upstream-only; ia-mirror
always writes whole files to disk and does not wrap partial byte-range fetches.docker build --pull --rm -f docker/Dockerfile -t ia-mirror:local docker
Multi-arch release build example:
docker buildx create --use --name ia-builder || true
docker buildx build --platform linux/amd64,linux/arm64 \
--build-arg IA_PYPI_VERSION=5.11.1 \
--build-arg PROJECT_VERSION=$(cat VERSION) \
-t themorgantown/ia-mirror:$(cat VERSION) --push -f docker/Dockerfile docker
python -m py_compile docker/fetcher.py
docker run --rm ia-mirror:local --print-effective-config
./tests/runtests.sh
app user.127.0.0.1 inside the container by default; exposure is opt-in via WEB_HOST=0.0.0.0 plus port publishing.WEB_SECRET_KEY is unset.WEB_CORS_ORIGINS only for trusted separate frontends.pip-audit, Dockerfile linting, image builds, and SBOM generation in CI.For local image scanning:
docker build -f docker/Dockerfile -t ia-mirror:local docker
docker scout quickview ia-mirror:local
docker scout cves ia-mirror:local
docker scout recommendations ia-mirror:local
The release gate is the full test suite in tests/runtests.sh. It covers Python backend tests, CLI integration tests, and Web UI integration tests.
To run everything:
./tests/runtests.sh
Test artifacts are written under tests/test_output/.
The server inside the container binds to 127.0.0.1 by default, which port publishing cannot reach. Pass -e WEB_HOST=0.0.0.0 with docker run (the bundled docker-compose.yml already sets it). The healthcheck still passes in this state because it runs inside the container.
docker run -d \
-v "$PWD/mirror:/downloads" \
-v "$PWD/ia-state:/data" \
-p 127.0.0.1:9090:17865 \
-e WEB_HOST=0.0.0.0 \
themorgantown/ia-mirror:latest
Then open http://localhost:9090.
Only one container should write to the same WEB_DB_PATH at a time.
Check:
WEB_RUNNER=realdocker logs <container>Check browser console errors, verify that the mapped Web UI port is reachable, and try a hard refresh.
If the image cannot find ia, verify that the image was built with the intended IA_PYPI_VERSION. If you hit a bug, open an issue at https://github.com/themorgantown/ia-mirror/issues.
Content type
Image
Digest
sha256:95a4edcd8…
Size
29.1 MB
Last updated
about 2 months ago
docker pull themorgantown/ia-mirror