Reproducible event photo sharing with Immich and Terraform
A disposable public photo album for one event on Oracle Cloud, seeded from the home instance and gone after the last upload
Overview#
The home Immich instance (the one in the homelab write-up) is for me: private library, private people, private everything. When a community event needs a shared photo collection, that instance is exactly the wrong thing to expose. So instead, for the VietPEI Tet Holiday 2026 event, the guests got their own Immich at example.com/s/sharing (“Happy Tet 2026!”, 156 shared photos and videos), provisioned by a single terraform apply and seeded from a locally curated copy.
The Terraform project behind it is written to be re-pointed at the next event: change the domain and the share link in terraform.tfvars, apply, done.
One seed, three artifacts#
What makes this reproducible is that an Immich share link is just a row in Postgres. Curating the event album locally means users, albums, the public share link, and all the metadata live in the database, and a database dump carries them to a clone. export-seed.sh builds three artifacts from the local instance:
# full dump: users, albums, the share link
docker exec -e PGPASSWORD=$DB_PASSWORD -t immich_postgres \
pg_dumpall --clean --if-exists -U $DB_USERNAME | gzip > seed.sql.gz
# library skeleton: folder structure and .immich markers, media excluded
tar --exclude='**/*.jpg' ... -czf library_skeleton.tar.gz library
# logos and profile pictures
tar -czf assets.tar.gz assetsbashThe skeleton is the subtle piece: Immich tracks imported files with hidden .immich marker files next to the media. Shipping the folder structure with markers but without the heavy media lets the clone know what should exist without carrying gigabytes. The assets bundle covers what guests see before uploading: the site logo and profile pictures.
Terraform: the box#
terraform apply stands up a self-contained stack on Oracle Cloud:
- a VCN with an internet gateway and a security list opening only 22, 80, 443
- an Ampere A1 instance (4 OCPUs, 24 GB RAM, ARM), Ubuntu, 200 GB boot volume for photos
- a reserved public IP, so the DNS A record never has to chase an ephemeral address
- cloud-init that installs Docker, writes the compose file and Caddyfile, and opens the host iptables for 80/443
The shape is deliberately boring. ARM keeps it inside the always-free tier, and 4 OCPUs handle upload transcoding for a room full of guests without tuning.
The provisioning step then SSHes in as the IP-attaching finishes and runs the restore: extract skeleton and assets, docker compose up database redis, wait for Postgres, then
zcat $SEED_FILE | sudo docker exec -i immich_postgres psql -U postgres -d immich
sudo docker compose up -dbashRe-running with a new event is: curate the new album at home, re-run export-seed.sh, change two tfvars (domain_name, share_link_id), apply.
The stack at the edge#
Four containers, no orchestration:
immich-server(:release) with the library on the boot volume- Postgres 14 with the vector extensions, pinned by image digest, because a half-restored ML database is not a debugging experiment you want mid-event
- Redis for jobs and sessions
- Caddy in front with ACME certificates, gzip, and two small jobs: serving
/logo.pngfrom the assets bundle, and the one-line redirect that makes the bare domain open the album
<event-domain> {
handle /logo.png { root * /srv; file_server }
encode gzip
redir / <share_link_id> temporary
reverse_proxy immich-server:2283
}textThat redirect is why the whole event runs on one short address, example.com/s/sharing: guests type the short domain and land on the album, no accounts, no instructions. Immich’s server-side rendering even fills the social preview card (“156 shared photos & videos”), so the link looks right when it is pasted into a group chat.
The site itself, captured live:


Why not just share the home instance?#
Three reasons, all learned the hard way:
- Privacy: the home library holds everything personal; an event instance holds exactly one curated album.
- Blast radius: a public URL will get crawled, probed and rate-limited; let it hit a disposable box whose
terraform destroyerases VCN, instance, and reserved IP in one command. - Bandwidth: event week uploads should not share a home uplink with the household’s Jellyfin.
Security and convenience pull against each other hardest here. The room is mostly parents and children, without the same technological comfort as the person running the server: most guests will not create an account or install an app just to see photos, and the existing options, commercial ones included, nearly all want exactly that. The invitation QR code was the balance that held: the URL is never advertised online, so the only people who learn it are the ones scanning the code at the venue. Guests still forward the link to family afterward, of course, but a link spreading person to person from the room is a different exposure than one posted publicly.
The personal instance stays on the Proxmox host, untouched. The event instance stays up for a while after the party, then the album is re-created as a shared album in VietPEI’s private Facebook group, where the association already operates and where both archival and sharing are easier than keeping a server alive. Only then does the instance disappear without a trace, and the next event starts from the same three artifacts.
Closing#
Total setup for the next event: curate the album locally, run ./export-seed.sh, fill in domain_name and share_link_id, terraform apply, point the DNS record at the output IP. The whole path from “we should collect photos for this” to a public share link is an afternoon, most of it upload time.