GitHub - openphotos-ca/openphotos: Own Your Photos, Forever Secure

6 min read Original article ↗

Try The Demo

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: DemoDocsDownloadGitHub 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

Email 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 3003 is reachable from those devices.

Option A: Use an installer

Download the latest release assets from GitHub Releases.

  • Windows: use the setup.exe asset.
  • macOS: use the .pkg asset.
  • Linux: use the one-line installer on common glibc + systemd distributions. It detects amd64 vs arm64 and starts installation. If a matching Linux tarball is in the current directory or beside a local install_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.gz files include the standard models. The online openphotos-linux-online_<version>_<arch>.tar.gz installers reuse openphotos_models.zip from 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 openphotos

For 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 sidecar rustus
  • 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/openphotos
  • rustus/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/openphotos
  • dist/linux/x86_64-unknown-linux-gnu/rustus
  • dist/linux/aarch64-unknown-linux-gnu/openphotos
  • dist/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.exe
  • dist/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_HOME or ANDROID_SDK_ROOT, or
  • creating android-java/local.properties with sdk.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:assembleDebug

Build 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)

  1. Build binaries:
cargo build --release --locked --no-default-features
cargo build --release --locked --manifest-path rustus/Cargo.toml --bin rustus
  1. 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
  1. 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)