Ashiq's Portfolio
Toggle sidebar
Self-Hosting on a Raspberry Pi...
Article
Updated Jul 13, 2026 6 min read

Self-Hosting on a Raspberry Pi with Docker

Docker Linux Raspberry Pi Self-Hosting Cloudflare
Self-Hosting on a Raspberry Pi with Docker

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:

  1. Monitoring and uptime checks, with alerts pushed to Telegram โ€” I find out a service is down from a message, not from a visitor.
  2. Automated backups of volumes and databases on a schedule: nightly mariadb-dump per database, volume snapshots for file data, shipped off the Pi. A backup that lives only on the machine it protects is not a backup.
  3. Resource limits per container so one service can't starve the others.
  4. 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

  1. Running from an SD card. It will work, then it will corrupt. NVMe or a USB SSD from day one.
  2. Skipping resource limits. The default is "unlimited", and unlimited on 8 GB is a lie.
  3. Exposing ports "just temporarily". The tunnel exists so you never have to; temporary port forwards have a way of becoming permanent.
  4. Trusting latest tags everywhere. Pin versions for anything stateful. mariadb:11 is a choice; mariadb:latest is a surprise.
  5. 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?
For personal projects, demos, and small-team tools โ€” comfortably, especially on an NVMe drive. The Pi 5 handles this portfolio, two demo Laravel apps, Nextcloud, and Jellyfin with headroom. What it's not is a substitute for redundant hosting when downtime costs money.
Do I need a static IP from my ISP?
No โ€” that's the quiet superpower of Cloudflare Tunnel. The connection is outbound from the Pi, so a changing home IP, CGNAT, or a locked-down router makes no difference.
How do you deploy Laravel updates to it?
Git pull, rebuild the image, 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?
An off-site replica of the backups is non-negotiable and already in place; the next experiment is a second node to practise failover โ€” because the only way to learn recovery is to need it on purpose.

Want help self-hosting your apps or a Laravel build in Dubai, UAE? Get in touch.