Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HomemadePasta 🍝

A self-hosted personal cloud, built on Docker Compose. One docker-compose.yml, one .env file, your own infrastructure.

What's included

Service Purpose
OpenCloud File sync and storage (Nextcloud-style) 🗂️
Immich Photo and video management 🎞️
Jellyfin Media streaming (movies, shows, music) 📺
Audiobookshelf Audiobook and podcast server 📚
Dawarich Personal location history 🗺️
lldap Lightweight LDAP identity backend 🫆
Authelia OIDC/SSO provider, sits in front of lldap 🪪
Cloudflare Tunnel Remote access without opening ports 🛜
EuroOffice In-browser document editing for OpenCloud 📑
MinIO S3-compatible object storage backing OpenCloud 🗃️
Watchtower Automatic container image updates 👀

Every service sits behind Authelia/lldap for identity and is only reachable through the Cloudflare Tunnel — nothing is exposed directly to the internet.

Architecture

  • Compose structuredocker-compose.yml at the root uses include: to pull in one compose file per service from services/*/. Adding a service means adding a new directory there and including it.
  • Identity/SSO — lldap is the LDAP backend; Authelia sits in front of it as the OIDC provider for every other service.
  • No shared database — each service that needs Postgres runs its own dedicated <service>_db container with its own bind-mounted data directory. There's no single shared database instance.
  • Network exposure — every service's host port is bound to 127.0.0.1 only. cloudflared is the sole public ingress point: it reaches every other service over the shared Docker network by container name, so it doesn't need a host port binding itself. Keep any new service's ports bound to 127.0.0.1 too — only the tunnel should ever front something publicly.
  • Data layout — nearly all persistent state is an explicit bind mount under docker_data/<service>/, which is why core/backup.sh/core/restore.sh can just rsync the whole docker_data/ tree instead of maintaining a list of Docker volumes. Two exceptions live on the Hetzner Storage Box instead of local disk (hetzner_smb/immich_upload, hetzner_smb/minio_data), and the media library Jellyfin/Audiobookshelf read from is a separate rclone WebDAV mount (data/media_mount) pointed at OpenCloud's "Media" project space.
  • Watchtower polls for image updates every 2 hours and prunes old images after updating.

Prerequisites

  • Docker and Docker Compose
  • rclone — mounts your cloud storage remote
  • cifs-utils — mounts your SMB/CIFS storage box
  • A domain you control, proxied through Cloudflare
  • Storage: a Hetzner Storage Box (or similar SMB-capable remote storage) for backups and media

Layout

This repo is one of three sibling directories that must live next to each other:

parent-dir/
├── HomemadePasta/   ← this repo
├── docker_data/     ← all container state (created by setup.sh)
└── hetzner_smb/     ← your SMB/CIFS storage mount (created by setup.sh)

Setup

  1. Clone this repo into its own directory (see layout above):

    git clone https://github.com/GustavOnsberg/HomemadePasta.git
    cd HomemadePasta
  2. Run the setup script — copies the env template and creates the sibling data directories:

    ./setup.sh
  3. Edit config/.env — replace every please_change_me placeholder with a real value, and fill in your domain, email, and cloud storage credentials. The comments in config/.env.template explain what each variable is for.

  4. Configure rclone — set up the opencloud-media remote, used to mount OpenCloud's "Media" project space for Jellyfin/Audiobookshelf:

    rclone config
  5. Start the stack:

    ./manage.sh start
  6. Wire up SSO manually — register OIDC clients in Authelia's config (services/authelia/) for each service, and create an OpenCloud app token for the media_admin user (used by core/media_mount.sh). This isn't automated.

Cloudflare Tunnel subdomains

cloudflared is the only public ingress point — every service's host port is bound to 127.0.0.1 only, so nothing is reachable except through the tunnel. The tunnel itself connects outbound to Cloudflare (no inbound firewall rule needed), and cloudflared reaches every service over the shared homemadepasta_default Docker network by container name — so the public hostname config below (set up in the Cloudflare dashboard under your tunnel's Public Hostname tab, not in this repo) is all that's needed to wire each one up.

Replace yourdomain.com with your actual DOMAIN from config/.env:

Public hostname Service Points to (internal)
auth.yourdomain.com Authelia (OIDC/SSO) http://authelia:9091
files.yourdomain.com OpenCloud http://opencloud:9200
eurooffice.yourdomain.com EuroOffice http://eurooffice:80
photos.yourdomain.com Immich http://immich-server:2283
audiobooks.yourdomain.com Audiobookshelf http://audiobookshelf:80
tv.yourdomain.com Jellyfin http://jellyfin:8096
timeline.yourdomain.com Dawarich http://dawarich-app:3000
ldap.yourdomain.com lldap admin UI http://lldap:17170

The first seven match the *_URL variables in config/.env — keep them in sync if you change a subdomain. ldap.yourdomain.com is the exception: it's hardcoded as LLDAP_HTTP_URL in services/lldap/lldap-compose.yml rather than sourced from .env.

Day-to-day commands

./manage.sh start          # Mount everything, bring the stack up, prune unused images/volumes
./manage.sh stop           # Bring the stack down, unmount everything (no backup)
./manage.sh restart        # stop + start
./manage.sh status         # Show container and mount status
./manage.sh snapshot_sync  # Back up, take a storage box snapshot, then restart the stack

./core/backup.sh            # rsync this project + docker_data to the backup destination
./core/restore.sh           # Interactively restore from that backup
./core/hetzner-snapshot.sh  # Trigger a point-in-time storage box snapshot

Cron jobs

manage.sh snapshot_sync is meant to run on a nightly cron job — it backs up, takes a storage box snapshot, and restarts the stack in one resilient pass (a failure in any one phase doesn't leave the stack down, it just warns and moves on to the next phase). Example crontab entry, run as whichever user has permissions on HomemadePasta/docker_data/hetzner_smb:

0 4 * * * cd /path/to/HomemadePasta && ./manage.sh snapshot_sync >> /path/to/HomemadePasta/cron.log 2>&1

This stops the stack, backs up, snapshots, and brings it back up once a day — expect a few minutes of downtime at that hour. cron.log is gitignored; check it if a scheduled run misbehaves.

License

Personal project, shared as-is. No warranty — read before you run it on anything that matters.

About

Self-hosted personal cloud: file sync, photos, media, and identity, all in Docker Compose

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages