The Personal Cloud
Replace Dropbox / Google Drive / iCloud.
Replace Dropbox / Google Drive / iCloud with three nodes you already own: a VPS for availability, a desktop for the bulk of your files, and a phone for automatic photo upload. Encrypted before it leaves any of them.
What you'll end up with: a vault called
Photosreplicated across three nodes (RF = 2 reliable + 1 mobile seeder), auto-syncing your phone's camera roll, browsable from any device's web UI.
Prerequisites
- Any small VPS — 1 vCPU, 1 GB RAM, 25 GB disk is enough for ~20 GB of replicated photos.
- Your usual desktop / laptop (Linux, macOS, or Windows).
- An Android phone with the MeshHold APK installed. iOS is not supported yet.
- A domain pointing at the VPS — optional, but it gives you free Let's Encrypt TLS.
1. Install the first node (VPS)
Start with the VPS — it's the only node guaranteed to be online, so it's our bootstrap peer.
# Ubuntu / Debian
ssh root@vps.example.com
wget https://meshhold.dev/dl/linux/meshhold-amd64.deb
apt-get install -y ./meshhold-amd64.deb
# Set a password for the web UI (daemon stays running)
sudo -u meshhold meshhold set-password
Prefer Docker? Same binary, no apt step, no system user. Works the same on any host with Docker installed:
bash curl -fsSL https://meshhold.dev/dl/docker/meshhold-amd64.tar.gz \ | gunzip | docker load docker volume create meshhold-data docker run -d --name meshhold --restart unless-stopped \ -p 7777:7777 -p 8080:8080 \ -v meshhold-data:/var/lib/meshhold \ -e MESHHOLD_PASSWORD=changeme \ meshhold:latest-amd64The image is distroless static and runs as uid 65532. The
meshhold-datanamed volume picks that ownership up automatically; a host-path bind-mount needschown 65532:65532first.
Open https://vps.example.com:8080/ and accept the self-signed cert (or
configure api.tls.acme_domain in config.yaml for Let's Encrypt). The
Setup Wizard greets you with three choices:
- Create new network — pick this on the first node; it mints a fresh swarm-key.
- Join existing — for nodes #2, #3, …
- Continue offline — for trying it out on a laptop with no peers yet.
2. Create a vault
In the web UI, click Vaults → New. Pick:
- Name:
Photos - Type:
storage - Key: any strong passphrase — write it down, this is the encryption key.
- Replication factor:
2(we want two reliable copies).
RF counts only reliable nodes. Your phone defaults to
reliable=false, so it seeds blocks but doesn't count toward the factor. That's the point — phones come and go; reliable nodes stay.
3. Pair your desktop
On the VPS, hit Network → Add peer and copy the invite QR or the
host:port string. On the desktop:
# Install the desktop binary (Linux example — Windows uses the MSI).
sudo apt-get install -y ./meshhold-amd64.deb
systemctl start meshhold
# Open https://localhost:8080/, pick "Join existing", paste the invite.
Within ~20 seconds the desktop appears in the VPS's Network page.
Open Vaults, you'll see Photos listed — click it and add the same
passphrase.
4. Pair your phone
- Install the APK and open the app.
- Tap Scan invite, point it at the QR from the VPS.
- Open Vaults → Photos, enter the passphrase.
- Toggle Camera-roll auto-upload. New photos appear on the other nodes in seconds.
5. Set replication and walk away
The defaults are already sane, but you can tune them on the VPS:
vaults:
- vault_id: "..."
name: "Photos"
key: "your-passphrase"
storage_path: "/var/lib/meshhold/photos"
full_sync: true # keep a full copy on the VPS
replication_factor: 2
type: "storage"
Restart the daemon. The VPS will pull and decrypt every block; the desktop already has a local copy of files it created; the phone keeps the encrypted ciphertext but can decrypt anything you scroll to.
6. Look at the photographs
Open Vaults → Photos and switch the view to the gallery — the third button next to the table and grid icons. It is a view of the vault, not a separate app: nothing to install, no second index, no import step.
The gallery reads each picture's own EXIF on the node that holds it, so it groups by the day the shutter fired rather than by the file's modification time — which for a photograph is usually the day it was copied off the phone, and would file a whole holiday under "today". Pictures that carry no date collect in an Undated section that says whether they are genuinely undated or simply not read yet.
It is recursive from wherever you are, so you never walk into 2019/08/,
and the folder structure is still available as a grouping if you organised
your pictures deliberately. Tile size, grouping and sort are per-device.
Press and hold a picture — or Ctrl-click on a desktop — to start selecting; then drag across tiles, Shift-click for a run, or tick a whole day from its heading. The bar that appears can download the selection as a single zip, move or copy it into another folder, or delete it. Deleting keeps earlier versions, and the confirmation says so.
Two things it will not do. A HEIC from an iPhone or a raw file from a camera shows as an icon: there is no decoder for either in a build that ships without cgo, though the date and the place are still read. And there is no grouping by place name and no map, because both would mean sending the coordinates of your own photographs to somebody else's service — the coordinates are shown in a picture's details panel and stay on your machines.
On a television the same gallery is driveable with the remote, with larger tiles by default.
7. Optional: turn on the media player
The music / video library and player are off by default — a fresh node is infrastructure, not a media server. To browse a vault as a media library and stream from it, turn the feature on for that node in Profile → Settings → Media library. The Player tab then appears in the navigation. It's a per-node switch: leave it off on headless servers that only store and replicate, turn it on where you actually watch and listen.
Troubleshooting
Nodes don't see each other
- Check the swarm-key matches on both —
meshhold keygenoutput, exactly. - Open
0.0.0.0:7777in the VPS firewall. - On the second node, paste the VPS's public address into
bootstrap_peers.
Phone keeps going offline
Battery optimisation. In Android settings, mark MeshHold as unrestricted (and add it to any OEM "protected apps" list) so the background daemon's foreground service isn't killed — that service is what keeps chat and call notifications arriving while the screen is off.
RF stuck at 1
Open the vault's Health panel. If only one reliable node is online,
RF can't climb. Bring up the second reliable node, or temporarily mark
a third as reliable: true in its config.
Next up
Add a chat vault for your household (see The Secure Comms Hub),
or expose Photos over S3 to back up your Immich instance (see
The S3-compatible Backend).