DEV Community

harsh
harsh

Posted on

Shipping a Gumroad Clone to Production

This is a walkthrough of the deployment setup behind my Gumroad clone: an EC2 box, Docker Compose, Nginx as the front door, and a GitHub Actions pipeline that does the boring parts so I don't have to SSH in and pray every time I ship a change.

The shape of it
Browser
│ HTTPS :443

Nginx (host)
├─► /api/ → gumroad-server container (:5000)
├─► /uploads/ → shared volume on disk
└─► / → gumroad-client container (:5173)

gumroad-server ─► Redis (cache/sessions)
gumroad-server ─► MongoDB Atlas (or local :27017 fallback)

Nginx runs on the host, not in a container — it terminates SSL and decides where a request goes before Docker ever sees it. The client and server each live in their own container, and Redis rides along as a third. Mongo isn't containerized in production at all; it's Atlas, with a local instance as a dev/fallback option.

Nginx:

The pipeline: build, ship, prove it's alive

The GitHub Actions workflow has three jobs that gate each other — no point building Docker images if lint fails, no point deploying if the build fails.

  1. Build & test — install deps, run lint. Fast fail if the code's broken.

  2. Docker build — build the server and client images with Buildx, using GitHub Actions cache so rebuilds aren't starting from zero every time. Registry push is stubbed out (push: false) until a registry's wired up — right now the images just prove they can build.

  3. Deploy — SSH into the box and do the actual work:

script: |
  cd /home/ubuntu/Gumroad-Clone
  git pull origin main
  docker compose build --no-cache server client
  docker compose down server client
  docker compose up -d server client redis

  sleep 10

  STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/health)
  if [ "$STATUS_CODE" -ne 200 ]; then
    echo "Error: Backend failed healthcheck with status $STATUS_CODE"
    docker compose logs server
    exit 1
  fi

  docker exec gumroad-server npm run seed:products
  echo "Deployment completed successfully! 🎉"
Enter fullscreen mode Exit fullscreen mode

What to check after every deploy
GET /health → 200 (confirms the DB connection is live, not just that the process started)
GET /api/v1/products/discover → returns the seeded products

If you're deploying something similar, the whole thing is genuinely just: one host, a reverse proxy, a couple of containers, and a pipeline that refuses to lie to you about whether the deploy worked.

Top comments (0)