Skip to content
Go back

From Homelab to Production: Launching an E-commerce Shop with Open Source Tech

Published:  at  05:00 PM

Remember when I showed you all the cool services running on my Raspberry Pi 5? Well, it turns out that same little machine is capable of much more than just home automation and other selfhosting staff. We recently launched an e-commerce shop, Zenoire and you guessed it - it’s running on that very same Pi 5.

This post is a deep dive into the tech, the tweaks and the real-world performance of running a small production shop on a tiny, self-hosted server. Spoiler alert: the results were surprisingly good.

The Tech Stack: Why These Choices?

Building a shop from scratch means you get to pick your favorite toys. Here’s what we went with and why:

You might be asking, “Why not Node.js for the backend?” I was curious too and tried it… Stick around and I’ll show you a memory usage comparison that makes the case for Go much clear.

Caddy: The Secret Weapon for Easy HTTPS

I can’t say enough good things about Caddy. Caddy handles everything you need, automatically.

Here’s my entire Caddyfile for the shop. It includes routing for two domains and some security headers.

(security_headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), vr=(), interest-cohort=(), sync-xhr=()"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'self'; form-action 'self';"
        Cross-Origin-Embedder-Policy "require-corp"
        Cross-Origin-Opener-Policy "same-origin"
        Cross-Origin-Resource-Policy "same-origin"
    }
}

lantern.zenoire.org {
    import security_headers

    # Route API requests to the backend
    handle_path /api/* {
        reverse_proxy lantern-zenoire-backend:3000
    }

    # Route all other requests to the frontend
    handle {
        reverse_proxy lantern-zenoire-frontend:3000
    }
}


zenoire.org {
    import security_headers

    # Route API requests to the backend
    handle /api/* {
        reverse_proxy zenoire-backend:3000
    }

    # Route all other requests to the frontend
    handle {
        reverse_proxy zenoire-frontend:3000
    }
}

That’s it. Caddy automatically provisions and renews SSL certificates from Let's Encrypt. The configuration is clean, readable and just perfectly works.

Docker Setup: Containerizing the Shop

Everything runs in Docker, orchestrated with Docker Compose. This makes our entire stack portable, reproducible and incredibly easy to manage.

Backend Dockerfile (Golang)

This is a classic multi-stage build. The final result is a secure minimal container.

# Stage 1: Build the Go binary
FROM golang:1.23-alpine AS builder
WORKDIR /app

# Install git for private dependencies if needed
RUN apk add --no-cache git

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

# Copy source and build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Stage 2: Create the final, minimal image
FROM alpine:latest
WORKDIR /root/

# Add certificates and timezone data
RUN apk --no-cache add ca-certificates tzdata

# Copy only the compiled binary from the builder stage
COPY --from=builder /app/main .

EXPOSE 3000
CMD ["./main"]

Frontend Dockerfile (Next.js)

The frontend build is also multi-stage to keep the final image as optimized as possible.

# Stage 1: Build the Next.js app
FROM node:20-alpine AS builder
WORKDIR /app
RUN npm install -g pnpm

# Cache and install dependencies
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --prod

# Copy source and build the app
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN pnpm build

# Stage 2: Create the final production image
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED 1
ENV HOSTNAME=0.0.0.0

# Copy only the necessary artifacts from the builder
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["node", "server.js"]

Why this setup is great:

Docker Compose Configuration

This file connects everything together. It defines the frontend and backend services and connects them to a shared network that Caddy container can also access.

version: '3.8'

services:
  backend:
    build: ./backend
    container_name: shop-backend
    restart: unless-stopped
    env_file:
      - ./backend/.env
    networks:
      - applications

  frontend:
    build: ./frontend
    container_name: shop-frontend
    restart: unless-stopped
    depends_on:
      - backend
    networks:
      - applications

networks:
  applications:
    external: true
    name: applications

With this in place, deploying the entire stack is as simple as running docker compose up -d.

Image Optimization: Speed Matters

In e-commerce, site speed directly impacts everything. Since images are often the heaviest part of a page, I got serious about optimizing them. A couple of open-source tools did wonders.

For PNGs (logos, icons), I used oxipng:

# -o 4: A high optimization level
# --strip safe: Removes non-critical metadata
# --alpha: Optimizes transparency
oxipng -o 4 --strip safe --alpha *.png

This command shrunk product photos by 45-60% with no visible loss in quality.

For JPGs (product photos), I used jpegoptim:

# --strip-all: Removes all metadata (EXIF data, comments)
# --all-progressive: Makes images load progressively for a better user experience
jpegoptim --strip-all --all-progressive *.jpg

This shrunk our product photos by another 30-40%.

The impact was very big:

You can easily automate this with a simple shell script in your CI/CD pipeline, ensuring all new images get optimized automatically.

Performance: Real Numbers from Production

This is the fun part. How does this setup actually hold up? Thanks to my Beszel monitoring setup, I have the data.

Golang Backend: Lean and Mean

Backend Resource Usage

The Go backend is incredibly efficient. It uses minimal resources.

Even under load, the memory usage rarely creeps above 15MB. It’s the perfect choice for a memory-constrained device like the Pi.

Node.js vs. Golang: A Tale of Two Backends

Before committing to Go, I built a prototype in Node.js. The difference in resource usage was huge.

That’s an 85-90% reduction in memory usage!

Memory Comparison Chart

On a Raspberry Pi sharing its 8GB of RAM with a lot of other services, saving over 80MB is a massive win. It means more space for everything else and a more stable system overall.

Lessons Learned From Taking the Pi to Production

  1. ARM is awesome. I was seriously impressed. I hit zero compatibility issues. Golang has fantastic ARM support and arm64 Docker images are common.
  2. Every Megabyte of RAM Counts. With only 8GB to go around, choosing efficient tech is critical. Golang was a game-changer here. That 85% memory reduction wasn’t just a cool statistic, it meant I could run more services without stressing the system.
  3. Startup Time Is a Feature. The Golang app starts in under 100ms, making deployments and restarts completely seamless. The Next.js app takes a few seconds, which required more careful orchestration during updates. Fast startup times make rollbacks and updates less stressful.
  4. Security Headers Are an Easy Win. Using Caddy’s snippet system, it took about 10 minutes to add strong security headers to the site. It’s a simple step that improves your security posture and makes security scanners happy.

What’s Next: A Sneak Peek at My New Project

While building the shop, I spent a lot of time working with various APIs. It sparked an idea and I’m now deep into building a web app to make interacting with Salesforce Data Cloud APIs much more intuitive.

Data Cloud Kit Screenshot

The goal is to create a clean, developer and non-tech user-friendly interface for exploring endpoints, testing calls and managing credentials securely.

Final Thoughts

So, can you run a real e-commerce shop on a Raspberry Pi? Absolutely. For small to medium operations, the combination of modern open-source tools, an efficient language like Go and some optimization makes it not just possible, but it’s practical.

The best part? The total hardware cost was under $200 and it uses about 15W of power. When you compare that to monthly cloud hosting fees, the value is very noticeable.

If you are a homelabber or just curious about self-hosting, I hope this inspires you to give it a try. The learning experience is priceless and there’s a unique satisfaction in running your own production infrastructure.



Previous Post
[Archived] I Lost My Keys, So I Built a Better Lost & Found for Poland: The Story of Znalazka.org
Next Post
My Raspberry Pi 5 Homelab: Powering Privacy, Automation and More!