You learn more about running software in production from a self-hosted home lab than from any tutorial. I run a multi-service setup on a Raspberry Pi 5 with Docker, Ubuntu Server, and Cloudflare Tunnel โ including the portfolio site you are reading right now โ and it has made me a sharper backend developer. Here's how it's built, what it costs, and what breaks.
Why self-host at all
Self-hosting gives you three things SaaS never will: full control of your data, a real-world DevOps playground, and a monthly bill of basically zero. For a developer, that middle point is the prize โ you get to operate the infrastructure your apps run on, not just write the apps.
The Pi runs a stack of services, each in its own container:
- Nextcloud for file storage and collaboration.
- Jellyfin for media streaming.
- MariaDB and MongoDB for app databases.
- Self-hosted Laravel apps โ this portfolio, an HRMS demo, and an e-commerce demo, each on its own subdomain.
The hardware conversation is short: a Pi 5 with 8 GB RAM, an NVMe SSD via a HAT (the single biggest upgrade โ SD cards are both slow and mortal), a decent power supply, and a case with a fan. All-in, it costs less than three months of a modest cloud VPS, and it draws a few watts.
Everything in Docker Compose
Containers keep the host clean and make every service reproducible. One docker-compose.yml per stack means I can rebuild the whole thing from scratch in minutes:
services:
app:
image: my-laravel-app:latest
restart: unless-stopped
environment:
- APP_ENV=production
depends_on:
- db
deploy:
resources:
limits:
memory: 512M
db:
image: mariadb:11
restart: unless-stopped
volumes:
- db-data:/var/lib/mysql
restart: unless-stopped means services come back on their own after a power cut โ important when the "data center" is a board on your desk. The memory limit matters just as much: on an 8 GB machine running a dozen containers, one leaky service without a limit can take down everything else. Limits turn "the whole Pi died overnight" into "one container restarted, and I got a Telegram message about it."
One ARM-specific note: the Pi is arm64, and while the ecosystem is far better than it used to be, you'll occasionally hit an image with no ARM build. Check for linux/arm64 support before designing a stack around an image โ or be prepared to build it yourself.
Exposing services safely with Cloudflare Tunnel
I never open a port on my router. Cloudflare Tunnel creates an outbound-only connection from the Pi to Cloudflare, then routes my domains to the right container:
# cloudflared config: hostname โ local service
ingress:
- hostname: hrms.example.com
service: http://hrms-app:80
- hostname: shop.example.com
service: http://shop-app:80
- service: http_status:404
The benefits:
- No inbound ports, so no exposed attack surface โ my home IP appears nowhere in DNS.
- Free TLS and DDoS protection in front of every service.
- Clean subdomains (
hrms.example.com,shop.example.com) mapped to containers.
One Laravel-specific detail that bites everyone the first time: behind a tunnel, your app sees requests from Cloudflare's proxy, not from the visitor. Configure trusted proxies and force HTTPS URL generation, or redirects and signed URLs will quietly generate http:// links and mixed-content warnings. In Laravel 12 that's a one-liner in bootstrap/app.php plus honouring X-Forwarded-* headers.
Keeping it healthy
A home lab that needs constant babysitting isn't worth it, so I automated the upkeep:
- Monitoring and uptime checks, with alerts pushed to Telegram โ I find out a service is down from a message, not from a visitor.
- Automated backups of volumes and databases on a schedule: nightly
mariadb-dumpper database, volume snapshots for file data, shipped off the Pi. A backup that lives only on the machine it protects is not a backup. - Resource limits per container so one service can't starve the others.
- Watchtower-style image updates for low-risk services, manual updates for the databases โ automatic major-version bumps of MariaDB are how you get a 2 a.m. restore rehearsal.
And rehearse the restore. Twice a year I actually restore a backup to a scratch directory and boot a service from it. The first rehearsal found a cron job that had been silently dumping an empty database for weeks โ a lesson far cheaper to learn on a home lab than on a client system.
What it actually costs
Roughly: the hardware (one-time, under the price of a mid-range phone accessory bundle), a domain per year, and single-digit watts of electricity โ pennies a day. Cloudflare Tunnel's free tier covers everything this setup needs. Compare that with the equivalent set of managed services โ file storage, media streaming, three hosted apps, two databases โ and the home lab pays for itself in the first few months. The real cost is your time, and that time is precisely the point: it converts directly into professional skill.
Common pitfalls
- Running from an SD card. It will work, then it will corrupt. NVMe or a USB SSD from day one.
- Skipping resource limits. The default is "unlimited", and unlimited on 8 GB is a lie.
- Exposing ports "just temporarily". The tunnel exists so you never have to; temporary port forwards have a way of becoming permanent.
- Trusting
latesttags everywhere. Pin versions for anything stateful.mariadb:11is a choice;mariadb:latestis a surprise. - Backups without restores. Untested backups fail at exactly the moment they're needed โ test the restore path, not the backup path.
What it taught me
Running this stack made abstract backend concepts concrete: networking, reverse proxies, TLS, container orchestration, and graceful recovery. When I deploy a Laravel app now, I understand the whole path from request to response โ because I own every layer it travels through.
A Raspberry Pi, Docker, and a weekend is all it takes to start. It's the best-value DevOps lab you can build.
FAQ
Is a Raspberry Pi really enough to host production apps?
Do I need a static IP from my ISP?
How do you deploy Laravel updates to it?
docker compose up -d โ wrapped in a small script. For the portfolio, Laravel's scheduler and queue workers run in their own containers beside the app, exactly as they would on a cloud box.What would you add next?
Want help self-hosting your apps or a Laravel build in Dubai, UAE? Get in touch.