You have picked Medusa v2 over Shopify because you want control — over the checkout, the data, the payment flow. Now you need to host it somewhere that does not cost $39/month before you sell a single product. Hetzner's ARM servers start at €4.85/month. Coolify gives you a Vercel-like deployment UI on your own metal. This guide covers the production setup — docker-compose, health checks, SSL, the admin-login failure that breaks most deployments (GitHub #11769), and the ARM64 migration deadlock (GitHub #16011) — with a TCO calculator so you can see exactly what self-hosting costs vs Shopify.
Need the India payment stack too?
Once the server is running, wire up Razorpay for UPI/cards/netbanking and cash on delivery + Shiprocket fulfillment to complete the India commerce stack.
Most Medusa deployment guides stop at "it works on localhost." Production breaks in different ways — CORS misconfiguration locks you out of the admin panel, point-release upgrades crash the build, ARM images need multi-arch Docker builds, and the workflow engine loses state on restart if you forget to switch from in-memory to Redis. I am Manoj, implementation lead at Mith Tech in Bengaluru, and we deploy open-source commerce and ERP stacks on Hetzner for Indian businesses every month.
The cost comparison — Medusa + Hetzner vs Shopify
The headline argument for self-hosting Medusa is cost. But how much cheaper is it really, once you factor in managed databases, backups, and your order volume?
Compare the monthly cost of running Medusa v2 on Hetzner (with Coolify, PostgreSQL, Redis, backups) vs a Shopify Basic plan with transaction fees and apps. Adjust server tier, order volume, staff, and paid plugins.
Medusa + Hetzner + Coolify
Shopify Basic
Monthly savings
$518.91
96% less
Annual savings
$6226.94
per year
Break-even
Month 1
No setup cost for Medusa
The calculation does not include developer time. If you have a developer on the team, self-hosting is dramatically cheaper. If you are hiring a freelancer to manage deployments, add $500–$1,000/month in labour — at which point Shopify makes sense until you hit ~2,000 orders/month where the 2% transaction fee overtakes the developer cost.
Which server tier fits your store
Hetzner's CAX line uses Ampere Altra ARM processors. They are cheaper per vCPU than x86, but you need ARM64-compatible Docker images. Medusa's official image supports multi-arch. The only catch is GitHub #16011 — medusa db:migrate can deadlock on ARM64 Linux. If you hit this, run the initial migration on an x86 instance (CX-series), then switch the running workload to ARM.
Select a Hetzner CAX server tier to see the SKU capacity, order volume, and concurrent user limits — with deployment notes for each.
SKU capacity
500–5,000 SKUs
Order volume
100–1,000/day
per day
Concurrent users
20–100 users
Production sweet spot for most Indian D2C brands. Runs Medusa + Redis comfortably. Use managed PostgreSQL. ARM64 docker images required (multi-arch builds).
The production issues that break deployments
Admin login fails in production (GitHub #11769)
This is the most reported Medusa deployment bug — 33 comments, 6 reactions. The admin panel loads, you enter credentials, and nothing happens. No error, no redirect, no feedback.
The root cause is ADMIN_CORS. In development, Medusa runs on localhost:9000 and the admin is on localhost:9000/app — same origin, no CORS issue. In production, if you access the admin via https://admin.your-store.com but ADMIN_CORS is set to https://api.your-store.com, the browser blocks the login request silently.
The fix: set ADMIN_CORS to the origin your admin panel is served from. If the admin is on the same domain as the API (https://api.your-store.com/app), set it to https://api.your-store.com. If you host the admin separately, set it to that domain.
Build crashes on point releases (GitHub #14474)
Medusa's dependency tree is large — a transitive zod version mismatch (GitHub #12933, 47 comments, 7 reactions) broke fresh installs across many environments with Package subpath './v3' is not defined by 'exports'. The fix is to pin your lockfile and test upgrades on staging before applying to production.
In your Dockerfile, use npm ci --omit=dev (not npm install) to install from the lockfile deterministically. Never run npm install in a production Docker build — it resolves versions dynamically and can pull a broken transitive dependency.
Workflow engine must be Redis
Medusa v2's workflow engine defaults to in-memory mode. This means workflow state (including completeCartWorkflow, which transitions a cart to an order) lives in process memory. When your container restarts — for a deploy, a crash, or Coolify's health-check restart — all in-flight workflows are lost. Orders get stuck in a "completing" state with no way to recover without manual database intervention.
Switch to the Redis workflow engine in medusa-config.ts. This requires a Redis instance (already in the docker-compose) and stores workflow state persistently.
The deployment — step by step
Four-tab code recipe — docker-compose.yml (Medusa + PostgreSQL + Redis), multi-stage Dockerfile, Coolify setup instructions, and an alternative nginx reverse proxy config.
# docker-compose.yml for Medusa v2 on Hetzner/Coolify
version: "3.8"
services:
medusa:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://medusa:secret@db:5432/medusa?sslmode=disable
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- COOKIE_SECRET=${COOKIE_SECRET}
- STORE_CORS=https://your-storefront.com
- ADMIN_CORS=https://admin.your-store.com
- AUTH_CORS=https://admin.your-store.com
ports:
- "9000:9000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: medusa
POSTGRES_PASSWORD: secret
POSTGRES_DB: medusa
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U medusa"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass redissecret
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "redissecret", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
redisdata:Provision the Hetzner server
Log into Hetzner Cloud Console. Create a new server: Location Falkenstein (lowest latency to most of Europe and India), Type CAX21 (ARM64, 4 vCPU, 8 GB RAM, €8.49/mo). Add your SSH public key. Enable the Cloud Firewall with rules: TCP 22 (SSH), TCP 80 (HTTP), TCP 443 (HTTPS) — deny all else.
Install Coolify
SSH into the server and run the install script. Coolify installs Docker, sets up its own PostgreSQL for metadata, and starts a web dashboard on port 8000. Create your admin account on first access.
Create the project in Coolify
In Coolify's dashboard: New Project → New Service → Docker Compose. Either connect your Git repository (Coolify pulls on push) or paste the docker-compose.yml directly. Set environment variables in Coolify's UI — JWT_SECRET, COOKIE_SECRET, ADMIN_CORS, STORE_CORS, AUTH_CORS.
Deploy and migrate
Click Deploy. Coolify builds the Docker image, starts the containers, and runs health checks. Once healthy, open the terminal in Coolify and run npx medusa db:migrate followed by npx medusa user -e admin@your-store.com -p YourSecurePassword.
Add domain and SSL
In your DNS provider, create an A record pointing api.your-store.com to your server's IP. In Coolify, go to the service settings → Domains → add api.your-store.com. Coolify provisions a Let's Encrypt certificate automatically and configures the reverse proxy.
Verify with the production checklist
Run through every item in the checklist below. Pay special attention to the critical items — CORS configuration, SSL, database backups, and the Redis workflow engine.
Production checklist
18-point interactive checklist covering server security, Docker configuration, database setup, Medusa application settings, and SSL — tick off each item as you verify it.
Server & networking
Coolify & Docker
Database & Redis
Medusa application
SSL & reverse proxy
What to know before you commit
You own the uptime. Shopify's SLA is 99.99%. Your Hetzner server's SLA is 99.9% — and that covers hardware, not your application. If Medusa crashes at 2am, you are the oncall. Coolify's health checks and auto-restart help, but they are not a substitute for monitoring. Set up uptime monitoring on the /health endpoint at minimum.
ARM64 compatibility. Most official Docker images (Node, PostgreSQL, Redis) support ARM64. Some community Medusa plugins only publish x86 images. Check your dependencies before committing to an ARM server. If any plugin is x86-only, use a CX-series (x86) server instead — pricing is similar.
Backup strategy. Hetzner's managed PostgreSQL includes daily backups with 7-day retention. If you self-host PostgreSQL, set up a pg_dump cron job to an S3-compatible bucket (Hetzner Object Storage is €0.005/GB/month). Medusa stores product images in the filesystem or S3 — back those up too.
Security patching. Hetzner does not patch your server OS — you do. Run apt update && apt upgrade regularly (or enable unattended-upgrades for security patches). Docker images should be rebuilt monthly to pick up base-image security fixes. The 15 HIGH-severity CVEs in transitive dependencies (GitHub #14993) are your responsibility to override in package.json.
+How much does it cost to self-host Medusa v2 on Hetzner?
A production-ready setup with a CAX21 server (4 vCPU, 8 GB RAM), managed PostgreSQL, backups, and a domain costs approximately €18–20/month ($19–21). This includes Medusa (free), Coolify (free), and Redis (runs on the same server). The TCO calculator above gives a precise breakdown for your specific setup.
+Can I run Medusa v2 on an ARM64 server?
Yes — Medusa's Docker images support ARM64, and Node.js, PostgreSQL, and Redis all have official ARM builds. The one known issue is GitHub #16011: medusa db:migrate can deadlock on ARM64 Linux. Workaround: run the initial migration on an x86 instance, then deploy the running app to ARM. After the initial migration, subsequent migrations generally work fine.
+Why does the Medusa admin panel not load in production?
The most common cause is ADMIN_CORS misconfiguration (GitHub #11769). The browser blocks login requests if the admin panel's origin does not match ADMIN_CORS. Set it to the exact origin (protocol + domain + port) that serves your admin panel. Also check AUTH_CORS — it must also include the admin origin.
+Should I use Coolify or deploy Medusa manually with Docker?
Coolify adds Git-push deployments, auto-SSL, health-check restarts, and a visual dashboard — the same workflow as Vercel but on your own server, for free. Manual Docker deployment with nginx gives you more control but requires writing systemd services, certbot renewal crons, and deployment scripts yourself. For most teams, Coolify saves 2–3 days of DevOps setup.
+How do I fix the zod subpath error when building Medusa?
The error Package subpath './v3' is not defined by 'exports' in zod/package.json (GitHub #12933) is a transitive dependency resolution issue. Fix it by adding "overrides": {"zod": "3.23.8"} to your package.json (use the version Medusa expects), deleting node_modules and the lockfile, then running npm install to regenerate the lockfile.
+What version of Medusa does this deployment guide cover?
This guide targets Medusa v2.18.0 (latest stable as of August 2026) with Coolify v4.0, PostgreSQL 16, Redis 7, and Node.js 20 LTS. The docker-compose and Dockerfile should work with any Medusa v2.x release — pin your version in package.json and test upgrades on staging.
+Is Hetzner reliable enough for production e-commerce?
Hetzner's cloud SLA is 99.9% uptime. Their data centres in Falkenstein, Nuremberg, and Helsinki are Tier III equivalent. For Indian stores, latency from Falkenstein to Mumbai is ~120ms — acceptable for API calls, and your CDN-served storefront is fast regardless. Many European and Indian e-commerce businesses run production on Hetzner.
+What happens if my Hetzner server goes down?
Coolify's Docker health checks restart the Medusa container automatically on crashes. For server-level failures, Hetzner's cloud API lets you create a snapshot and restore to a new server in minutes. Enable Hetzner's automated backups (20% of server cost) for daily server-level snapshots. For zero-downtime, deploy behind a load balancer with two servers — but this is overkill for most stores under 1,000 orders/day.
Medusa on Hetzner with Coolify runs for under $20/month and gives you full control over every line of code in your commerce stack. The trade-off is operational responsibility — you own the uptime, the backups, and the security patches. If you have a developer who can manage Docker containers, that trade-off is worth it from day one. If you do not, start on Shopify and migrate when revenue justifies the engineering time.
Need help deploying Medusa on your own infrastructure?
We deploy headless commerce stacks on Hetzner, AWS, and bare-metal for Indian businesses. If you want the self-hosted cost savings without the DevOps overhead, we will set it up and hand you a working production environment.