Open Source • Self-Hosted • Privacy-First
Your Photos. Your Keys. Your Album Tree.
OpenPhotos is a self-hosted photo platform with locked albums (E2EE), nested albums, AI-powered discovery, and resumable uploads.
Links: Demo • Docs • Download • GitHub Releases
1. Demo
Access the demo here: https://demo.openphotos.ca
For the mobile app, use https://demo.openphotos.ca for the Server Endpoint URL.
Download the iOS app from the App Store: OpenPhotos
Login Credentials
| Password | |
|---|---|
demo@openphotos.ca |
demo |
2. Quick Start
This is the shortest path to a working OpenPhotos server for evaluation or a first home install. If you plan to build from source, skip to Build from source code.
Requirements
- Use a computer, Raspberry Pi, or NAS that can stay online while clients upload and index media.
- A good starting point is at least 4 GB of RAM.
- Supported host options include Windows 64-bit, macOS on Apple Silicon or Intel, 64-bit Linux, or any device that can run Docker and Docker Compose.
- If phones or other computers need to reach the server on your LAN, make sure port
3003is reachable from those devices.
Option A: Use an installer
Download the latest release assets from GitHub Releases.
- Windows: use the
setup.exeasset. - macOS: use the
.pkgasset. - Linux: use the one-line installer on common glibc + systemd distributions. It detects
amd64vsarm64and starts installation. If a matching Linux tarball is in the current directory or beside a localinstall_linux.sh, the installer uses that local file instead of downloading the tarball from GitHub:
curl -fsSL https://raw.githubusercontent.com/openphotos-ca/openphotos/refs/heads/main/scripts/install_linux.sh | sudo env "PATH=$PATH" bash
- Linux release assets include small wrapper scripts, smaller online tarballs, and larger bundled
tarballs. The bundled
openphotos-linux_<version>_<arch>.tar.gzfiles include the standard models. The onlineopenphotos-linux-online_<version>_<arch>.tar.gzinstallers reuseopenphotos_models.zipfrom the same folder when present, or download models from GitHub during setup. With the one-line installer, run the command from the folder containing the local tarball and model ZIP. - iOS client: install OpenPhotos on the App Store.
- Other downloads are also listed on the OpenPhotos download page: https://openphotos.ca/download/index.html.
Native installers provision or reuse models during setup when needed, so the first install can take longer than later reinstalls.
Linux uninstall uses the installed helper. The default keeps data and model folders; --purge
removes data, config, logs, and models too:
sudo openphotos-uninstall sudo openphotos-uninstall --purge
Option B: Use Docker
Create a working directory:
mkdir openphotos
cd openphotosFor Intel / AMD CPUs:
curl -L -o compose.yaml https://raw.githubusercontent.com/openphotos-ca/openphotos/refs/heads/main/docker/compose.yaml curl -L -o .env https://raw.githubusercontent.com/openphotos-ca/openphotos/refs/heads/main/docker/openphotos.env.amd64 sudo docker compose up -d
For ARM64 CPUs:
curl -L -o compose.yaml https://raw.githubusercontent.com/openphotos-ca/openphotos/refs/heads/main/docker/compose.yaml curl -L -o .env https://raw.githubusercontent.com/openphotos-ca/openphotos/refs/heads/main/docker/openphotos.env.arm64 sudo docker compose up -d
The default .env is fine for a first test install. Review storage paths before importing a full
library.
Start and verify
If you use an installer, the server usually starts automatically after installation. If you use
Docker, sudo docker compose up -d starts it.
Verify the server health endpoint:
curl http://localhost:3003/ping
Open the web app:
From another device on the same network, replace localhost with the server's LAN IP address:
On a phone or tablet, do not use localhost; it points to the phone itself. Use the same reachable
server URL you use from the browser, such as http://<server-ip>:3003/.
Back up a small album first so you can confirm upload speed, metadata, and thumbnail generation before importing a full library.
3. Build from source code
The source tree includes:
- Rust server binaries:
openphotos+ TUS sidecarrustus - Static web app:
web-photos - Android app source:
android-java - Android release helper:
scripts/build_android_installer.sh - Docker / NAS deployment files:
Dockerfile,compose.yaml,docker/,docs/docker.md - Docker build helper for local images:
scripts/build_docker_image.sh
Build Server (No Installer)
Download Models (Before Build)
GitHub source uploads do not include large model binaries. Download runtime models first:
This populates required runtime files under models/ (CLIP + face models).
macOS (native build)
cargo build --release --no-default-features --bin openphotos cargo build --release --manifest-path rustus/Cargo.toml --bin rustus
Build outputs:
target/release/openphotosrustus/target/release/rustus
Linux (Docker cross-build via script)
DOCKER_IMAGE="${DOCKER_IMAGE:-rust:1.90-bookworm}" USE_LIBHEIF=0 ./start.sh --build-linux --linux-target x86_64-unknown-linux-gnu DOCKER_IMAGE="${DOCKER_IMAGE:-rust:1.90-bookworm}" USE_LIBHEIF=0 ./start.sh --build-linux --linux-target aarch64-unknown-linux-gnu
Build outputs:
dist/linux/x86_64-unknown-linux-gnu/openphotosdist/linux/x86_64-unknown-linux-gnu/rustusdist/linux/aarch64-unknown-linux-gnu/openphotosdist/linux/aarch64-unknown-linux-gnu/rustus
Windows (Docker cross-build via script)
./start.sh --build-windows --windows-target x86_64-pc-windows-msvc
Build outputs:
dist/windows/x86_64-pc-windows-msvc/openphotos.exedist/windows/x86_64-pc-windows-msvc/rustus.exe
Build Web Client
Build output:
web-photos/out
Docker / NAS Deployment
Use the public container image:
cp docker/openphotos.env.example .env docker compose up -d
Build the image locally from source:
scripts/build_docker_image.sh --oss --platform linux/amd64 OPENPHOTOS_IMAGE=openphotos:local docker compose up -d
For ARM NAS devices, switch the build platform to linux/arm64.
Local Docker builds use the same Linux ffmpeg bundle root as the Linux installers:
If you keep those bundles somewhere else, pass:
scripts/build_docker_image.sh --oss --platform linux/amd64 --ffmpeg-bundle-dir /path/to/linux-ffmpeg
Build and push the architecture-specific images with the release helper scripts:
scripts/build_docker_amd64.sh scripts/build_docker_arm64.sh
The amd64 script builds openphotos:amd64, tags it as
ghcr.io/openphotos-ca/openphotos-amd64:oss, and pushes it to GHCR. The arm64 script builds
openphotos:arm64, tags it as ghcr.io/openphotos-ca/openphotos-arm64:oss, and pushes it to GHCR.
Run these commands from the repository root on a machine with Docker Buildx configured and an
authenticated docker login ghcr.io account that can push to openphotos-ca. Extra build options
accepted by scripts/build_docker_image.sh, such as --fresh, --pull, --no-cache, and
--ffmpeg-bundle-dir /path/to/linux-ffmpeg, can be passed through to either script.
The Compose deployment stores all persistent app data under /data in the container. Change OPENPHOTOS_DATA_MOUNT in .env to use a NAS bind mount instead of the default named volume.
Additional Docker / NAS notes are in:
docs/docker.md
Build Android App
The exported source includes the Android project under android-java.
Before building, make sure the Android SDK is discoverable either by:
- setting
ANDROID_HOMEorANDROID_SDK_ROOT, or - creating
android-java/local.propertieswithsdk.dir=/absolute/path/to/Android/sdk
The export intentionally does not include android-java/local.properties because that file is machine-specific.
Build a debug APK:
cd android-java
./gradlew :app:assembleDebugBuild output:
android-java/app/build/outputs/apk/debug/app-debug.apk
Optional release APK helper:
scripts/build_android_installer.sh
The helper tries ANDROID_HOME / ANDROID_SDK_ROOT first, then common Android SDK install locations, and writes android-java/local.properties automatically when needed.
It always produces an installable signed release APK.
If ANDROID_KEYSTORE_* env vars are not provided, it auto-generates and reuses:
android-java/.openphotos-signing/openphotos-auto-release.jks
Back up that keystore file. Future app updates must use the same signing key.
Build output:
dist/android-packages/openphotos-android-release.apk
Start Servers (macOS, Linux, Windows)
Run these from the repository root after models are downloaded.
macOS
Linux
Windows (PowerShell)
- Build binaries:
cargo build --release --locked --no-default-features cargo build --release --locked --manifest-path rustus/Cargo.toml --bin rustus
- Start Rustus (Terminal 1):
$env:RUSTUS_SERVER_HOST="127.0.0.1" $env:RUSTUS_SERVER_PORT="1081" $env:RUSTUS_URL="/files" $env:RUSTUS_DATA_DIR="$PWD\data\uploads" $env:RUSTUS_INFO_DIR="$PWD\data\uploads" $env:RUSTUS_TUS_EXTENSIONS="creation,termination,creation-with-upload,creation-defer-length,concatenation,checksum" $env:RUSTUS_MAX_BODY_SIZE="52428800" $env:RUSTUS_HOOKS="pre-create,post-finish" $env:RUSTUS_HOOKS_FORMAT="v2" $env:RUSTUS_HOOKS_HTTP_URLS="http://127.0.0.1:3003/api/upload/hooks" $env:RUSTUS_HOOKS_HTTP_PROXY_HEADERS="Authorization,X-Request-ID,Cookie" $env:RUSTUS_LOG_LEVEL="INFO" New-Item -ItemType Directory -Force -Path "$PWD\data\uploads" | Out-Null .\rustus\target\release\rustus.exe
- Start OpenPhotos (Terminal 2):
.\target\release\openphotos.exe --model-path models --database data --log-level info
Health checks:
http://127.0.0.1:3003/ping(OpenPhotos API)http://127.0.0.1:1081/health(Rustus)