# iCloud-docker for a scheduled, download-only iCloud backup

> iCloud-docker packages the Python iCloudPy client in a container and periodically downloads selected iCloud Drive documents and photos to a mounted directory. It suits users who need a simple read-side sync target, but it does not upload local changes and its password-free mode has a documented trust-expiry boundary.

**mandarons/icloud-docker** — Dockerized iCloud Client - make a local copy of your iCloud documents and photos, and keep it automatically up-to-date.

- Repository: https://github.com/mandarons/icloud-docker
- Stars: 1,892 · Forks: 74
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/mandarons-icloud-docker

## Why iCloud-docker exists on a server

iCloud-docker addresses a narrow gap between using Apple's consumer clients and operating an iCloud copy on a headless host. The repository describes periodic synchronization of selected or all iCloud Drive and Photos data using an iCloud username and password. That makes it relevant to people running Docker on a home server, NAS, or Linux machine who want files to arrive locally without manually opening the iCloud applications on every client.

The project is primarily a deployment wrapper around iCloudPy. The application code runs in Python, configuration is handled through YAML, and the Docker image supplies the runtime environment. This design is useful when the desired destination is a mounted host directory such as `/icloud`. It also means that iCloud-docker should not be confused with a general-purpose cloud synchronization client. The README explicitly states that the application only downloads files from the server and does not upload local files yet.

That boundary determines the appropriate use. A copy created by this project is suitable for retaining downloaded documents or photos, exposing them through another service, or collecting an additional server-side representation. It is not suitable for editing a file on the server and expecting that edit to return to iCloud Drive. Users who need bidirectional synchronization need a tool whose documented upload path is the opposite of this project's design.

## The Python client, container, and volume model

The runtime image is based on `python:3.10-alpine3.22`. A builder stage installs the Python requirements, while the runtime stage copies the installed packages and application files into an Alpine container. The dependency set includes `icloudpy==0.10.0`, `ruamel.yaml==0.19.1`, `python-magic==0.4.27`, `requests~=2.34.2`, and `flask==3.1.3`. Based on this layout, iCloudPy performs the iCloud interaction, requests handles network traffic, Ruamel handles YAML configuration, and Flask supports the optional web interface.

The entry point creates a non-root identity through `su-exec`. The Dockerfile defines default identity values of `PUID=911` and `PGID=911`, while the Compose example allows the host user identifiers to be supplied through the environment. The downloaded data is written through the mounted `/icloud` volume. Configuration, logs, session state, and optional web-interface state belong under `/config`, so persisting that host directory is part of an operational deployment rather than an optional refinement.

Authentication has two distinct state models. With `ENV_ICLOUD_PASSWORD`, or after accepting the `icloud` prompt to save the password, the container can reauthenticate after a trusted session expires. Without a stored password, it uses `/config/session_data` until Apple stops accepting that session. The README further explains that the web dashboard can refresh trust while the session remains alive. These paths affect both unattended operation and the sensitivity of files placed in the configuration volume.

## Docker Hub installation and initial authentication

The shortest documented installation mounts local directories, selects the configuration file, and starts the published image. Port `8080` is published for the optional web UI, but the README says it can be omitted unless `app.web_ui.enabled` is enabled.

```bash
docker run --name icloud \
  -v ${PWD}/icloud:/icloud \
  -v ${PWD}/config:/config \
  -e ENV_CONFIG_FILE_PATH=/config/config.yaml \
  -p 8080:8080 \
  mandarons/icloud-drive
```

Before this container can perform its first synchronization, the host's `${PWD}/config` directory must contain a `config.yaml`. The README excerpt does not provide the complete sample, so it does not establish the exact settings needed for a particular Drive or Photos selection. It would be a mistake to invent those keys. The administrator should copy the repository's `config.yaml` into the mounted configuration directory and adjust it according to that file.

If the password was not supplied and authentication has not yet completed, run the documented interactive command inside the container:

```bash
docker exec -it icloud /bin/sh -c "su-exec abc icloud --username=<icloud-username> --session-directory=/config/session_data"
```

The user follows the displayed authentication steps, including any required second factor. The `--session-directory=/config/session_data` argument keeps the resulting session in the persisted configuration volume. Users of Apple's China region add the documented region flag:

```bash
docker exec -it icloud /bin/sh -c "su-exec abc icloud --username=<icloud-username> --region=china --session-directory=/config/session_data"
```

The first real use is therefore configuration, container creation, and interactive authentication. Once those steps are complete, the application runs its periodic synchronization cycle and writes downloaded content through `/icloud`. The README does not document a one-shot command for forcing or monitoring that first cycle.

## Password storage, trust expiry, and unattended sync

iCloud-docker supports unattended operation without treating every authentication expiration as a permanent failure, but the two supported modes have different costs. With `ENV_ICLOUD_PASSWORD`, the password is copied into Python's keyring. On the documented headless Alpine environment, `keyring` resolves to `keyrings.alt.file.PlaintextKeyring`, which stores it in cleartext at `/config/python_keyring/keyring_pass.cfg`. Because an Apple ID password can affect more than document synchronization, this is an account-wide security decision rather than a minor configuration preference.

The password-free mode keeps the saved session in `/config/session_data` and avoids writing password-derived data. The container can retry after Apple stops accepting the session, but it cannot perform a credential sign-in because no password is stored. The README names the stopping error as `No Apple ID password is stored and the saved session is no longer valid ...`. It also states that `app.trust_expiry_warn_days` can produce an earlier warning and that `app.trust_refresh_days` rolls the accepted window forward while the session is healthy.

There is an important caveat in the documentation: refresh behavior has not been verified against Apple over a full trust lifetime in password-free mode. Apple controls how long a trust window lasts, and the project does not claim a fixed interval. This makes password-free operation attractive for reducing credential exposure, but not fully predictable. A human must still be available for interactive authentication when the session expires.

Moving an existing container into password-free mode requires both removal of `ENV_ICLOUD_PASSWORD` and deletion of the existing keyring entry. An empty environment value counts as unset, but merely changing the Compose file does not remove a password copied earlier:

```bash
docker exec icloud su-exec abc python3 -c "import keyring; keyring.delete_password('icloudpy://icloud-password', '<icloud-username>')"
```

The command prints `PasswordDeleteError` when no entry existed. That output is a useful confirmation that there was nothing to delete, not a sign that the container itself failed.

## Compose persistence, identity, and the optional Flask UI

For a long-running installation, the documented Compose configuration defines `restart: unless-stopped`, which asks Docker to start the service again unless it is explicitly stopped. It persists `/icloud` and `/config`, mounts the host timezone files read-only, and supplies identity values through an `.env.icloud` file. The documentation requires `ENV_CONFIG_FILE_PATH=/config/config.yaml` in that file and says it may also contain `ENV_ICLOUD_PASSWORD`.

```yaml
services:
  icloud:
    image: mandarons/icloud-drive
    environment:
      - PUID=<insert the output of `id -u $user`>
      - PGID=<insert the output of `id -g $user`>
    env_file:
      - .env.icloud
    container_name: icloud
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - ${PWD}/icloud:/icloud
      - ${PWD}/config:/config
```

The two mounts have different jobs. `/icloud` is the user-facing synchronized file store. `/config` contains configuration and authentication state, including the session, logs, and possibly the plaintext keyring file. A filesystem or backup policy that protects one mount may provide inappropriate treatment of the other, especially if `/config` is included in an image-level or host-level backup.

The web UI is disabled by default despite the image exposing port `8080`. The README documents a refresh-trust control that can work without a stored password while the session remains alive. It does not document authentication protection for the web interface, a TLS configuration, or a reverse-proxy setup. Publishing `8080:8080` to an untrusted network without resolving those points would expose more than the minimum needed for a headless sync container.

## Download-only behavior and operational limits

The most consequential limitation is stated directly in the README: local files are not uploaded to iCloud. A file changed under `/icloud` may therefore be overwritten or ignored according to the download workflow, and the project should not be represented as an editable iCloud Drive replica. Users who need local changes to propagate to Apple must look elsewhere.

The project is also an account client, not a general-purpose backup suite. The description promises local copies of documents and photos, but it does not claim version history, deletion retention, corruption recovery, point-in-time recovery, or verification that the local copy is complete. Docker volume persistence also does not protect the host directory from disk failure, accidental deletion, or filesystem corruption. A separate copy or snapshot policy would be needed for those concerns, although the repository does not prescribe one.

Session persistence is another boundary. Backing up only `/icloud` preserves downloaded content but omits session and configuration state. Backing up all of `/config` can instead capture credentials in the documented plaintext keyring file. Migration and upgrade planning must treat `/icloud` and `/config` as separate data classes.

The web interface is also optional rather than central. It adds Flask and an exposed service, but the visible README does not describe access control, multi-user support, API stability, or a reverse-proxy configuration. Operators who need only scheduled downloads have little reason to publish the port.

## Maintenance, releases, and BSD-3-Clause implications

The repository is not archived, and its last push was on 2026-10-14. Recent tags include `v2.1.0` on 2026-10-01, `v2.0.0` on 2026-08-07, and `v1.28.0` on 2026-07-27. These dates indicate recent release work, although the repository description and README do not provide a formal support lifetime for each release.

The versioned runtime dependency on `icloudpy==0.10.0` is an important upgrade cost. Changes in iCloud authentication or API behavior can therefore affect synchronization even when the container itself remains stable. The image also uses a fixed Python 3.10 and Alpine 3.22 base, so adopting a new application tag may also replace the base image and system packages.

The project is licensed under BSD-3-Clause. That license permits use and redistribution subject to its conditions, while preserving the license notice and disclaimer. The repository layout also includes a `LICENSE` file. Organizations still need to comply with the actual license text when distributing images or incorporating code, but the BSD-3-Clause designation does not itself certify compatibility with a particular deployment environment.

A direct alternative is to run iCloudPy outside Docker and implement scheduling, identity management, session persistence, logging, and monitoring in the host system. iCloud-docker is based on iCloudPy, so that approach keeps the client library but removes the packaged web interface and container boundary. The README does not document an official host-install procedure for iCloudPy in this repository, so an adopter would need separate installation instructions rather than assuming the Docker configuration transfers directly.

## Conclusion

Adopt iCloud-docker for a server-side, download-only copy of iCloud Drive or Photos when persistent volumes, scheduled retries, and container-based authentication are acceptable. Do not adopt it as a two-way synchronizer or as a full backup system for mutable local files. Before deployment, verify that the host filesystem preserves ownership, inspect the mounted configuration and session directories, and decide explicitly whether storing the Apple ID password in the container's plaintext keyring is acceptable for the account.

## FAQ

### Does iCloud-docker upload local changes to iCloud Drive?

No. The README explicitly says the application only downloads files from iCloud and does not upload local files.

### How do I authenticate an iCloud-docker container manually?

Run the documented `docker exec -it` login command as the container's `abc` user and pass `--session-directory=/config/session_data`. Authentication may require completing the displayed two-factor steps.

### Can iCloud-docker run unattended without storing an Apple ID password?

It can resume and refresh a saved session while Apple accepts it. Once that session becomes invalid, password-free mode cannot sign in again and waits for interactive authentication.

### Where does iCloud-docker store a saved Apple ID password?

On the documented headless Alpine environment, Python's keyring uses `keyrings.alt.file.PlaintextKeyring`. The password is stored in cleartext in `/config/python_keyring/keyring_pass.cfg`.

### Is the iCloud-docker web interface required?

No. The interface is enabled through `app.web_ui.enabled` and is off by default. If it is not enabled, the README says the `8080:8080` port mapping can be removed.

## Sources

- [Issues](https://github.com/mandarons/icloud-docker/issues)
- [License: BSD-3-Clause](https://github.com/mandarons/icloud-docker/blob/main/LICENSE)
- [mandarons/icloud-docker on GitHub](https://github.com/mandarons/icloud-docker)
- [README](https://github.com/mandarons/icloud-docker/blob/main/README.md)
- [Releases](https://github.com/mandarons/icloud-docker/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mandarons-icloud-docker
