docker.recipes

Medusa Headless Commerce

intermediate

Open-source Shopify alternative with headless architecture.

Overview

Medusa is a modern Node.js-based headless commerce platform that provides a flexible alternative to Shopify, focusing on API-first architecture and developer experience. Built with TypeScript and designed for customization, Medusa offers comprehensive e-commerce functionality including product management, order processing, customer management, and payment handling through a robust REST API. The platform emphasizes modularity and extensibility, allowing developers to build custom storefronts using any frontend technology while maintaining powerful backend commerce capabilities. This stack combines Medusa's commerce engine with PostgreSQL for transactional data integrity, Redis for session management and caching, and MinIO for self-hosted object storage, creating a complete commerce infrastructure. PostgreSQL ensures ACID compliance for critical order and payment data, while Redis provides fast session storage and job queue processing for background tasks like email notifications and inventory updates. MinIO handles product images, digital downloads, and media assets with S3-compatible APIs, eliminating dependency on external cloud storage providers. E-commerce startups, agencies building custom storefronts, and companies migrating from monolithic platforms like Shopify Plus will benefit from this stack's flexibility and cost-effectiveness. The headless architecture enables omnichannel commerce experiences across web, mobile, and IoT devices while maintaining centralized inventory and order management through Medusa's comprehensive admin interface.

Key Features

  • Complete headless commerce API with product catalog, inventory, orders, and customer management
  • Multi-region and multi-currency support with localized pricing and tax calculation
  • Built-in payment provider integrations including Stripe, PayPal, and manual payment methods
  • Advanced product variant management with unlimited options and custom attributes
  • Discount engine supporting percentage, fixed amount, and buy-X-get-Y promotional rules
  • PostgreSQL JSONB fields for flexible product metadata and custom attributes storage
  • Redis-powered job queue system for asynchronous email sending and webhook processing
  • MinIO integration for product images, digital downloads, and customer file uploads with S3-compatible URLs

Common Use Cases

  • 1Building custom D2C e-commerce stores with React, Vue, or Next.js frontends
  • 2Creating multi-tenant marketplace platforms with separate vendor catalogs and commission tracking
  • 3Developing mobile commerce apps with native iOS/Android frontends consuming Medusa APIs
  • 4Implementing B2B wholesale portals with custom pricing tiers and approval workflows
  • 5Setting up subscription commerce platforms with recurring billing and customer portals
  • 6Deploying white-label e-commerce solutions for agencies serving multiple clients
  • 7Creating omnichannel retail experiences connecting online stores with POS systems

Prerequisites

  • Minimum 4GB RAM (2GB for PostgreSQL, 1GB for Medusa server, 512MB each for Redis and MinIO)
  • Node.js 18+ knowledge for Medusa customization and plugin development
  • PostgreSQL administration skills for backup, maintenance, and performance tuning
  • Understanding of JWT authentication and session management concepts
  • Ports 7001, 9000, and 9001 available for admin interface, API server, and MinIO console
  • Basic S3 API knowledge for implementing file upload workflows in custom frontends

For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms

docker-compose.yml

docker-compose.yml
1services:
2 medusa-server:
3 build:
4 context: .
5 dockerfile: Dockerfile
6 ports:
7 - "9000:9000"
8 environment:
9 NODE_ENV: development
10 DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
11 REDIS_URL: redis://redis:6379
12 JWT_SECRET: ${JWT_SECRET}
13 COOKIE_SECRET: ${COOKIE_SECRET}
14 MINIO_ENDPOINT: minio
15 MINIO_BUCKET: medusa
16 MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
17 MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
18 depends_on:
19 postgres:
20 condition: service_healthy
21 redis:
22 condition: service_started
23 networks:
24 - medusa-net
25 restart: unless-stopped
26
27 medusa-admin:
28 image: medusajs/admin:latest
29 ports:
30 - "7001:80"
31 environment:
32 MEDUSA_BACKEND_URL: http://medusa-server:9000
33 depends_on:
34 - medusa-server
35 networks:
36 - medusa-net
37 restart: unless-stopped
38
39 postgres:
40 image: postgres:16-alpine
41 environment:
42 POSTGRES_USER: ${POSTGRES_USER}
43 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
44 POSTGRES_DB: ${POSTGRES_DB}
45 volumes:
46 - postgres_data:/var/lib/postgresql/data
47 healthcheck:
48 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
49 interval: 10s
50 timeout: 5s
51 retries: 5
52 networks:
53 - medusa-net
54 restart: unless-stopped
55
56 redis:
57 image: redis:7-alpine
58 volumes:
59 - redis_data:/data
60 networks:
61 - medusa-net
62 restart: unless-stopped
63
64 minio:
65 image: minio/minio:latest
66 ports:
67 - "9001:9001"
68 environment:
69 MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
70 MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
71 volumes:
72 - minio_data:/data
73 command: server /data --console-address ":9001"
74 networks:
75 - medusa-net
76 restart: unless-stopped
77
78volumes:
79 postgres_data:
80 redis_data:
81 minio_data:
82
83networks:
84 medusa-net:
85 driver: bridge

.env Template

.env
1# Medusa Configuration
2JWT_SECRET=$(openssl rand -hex 32)
3COOKIE_SECRET=$(openssl rand -hex 32)
4
5# PostgreSQL
6POSTGRES_USER=medusa
7POSTGRES_PASSWORD=secure_postgres_password
8POSTGRES_DB=medusa
9
10# MinIO
11MINIO_ACCESS_KEY=minioadmin
12MINIO_SECRET_KEY=secure_minio_password

Usage Notes

  1. 1Create Medusa project: npx create-medusa-app
  2. 2Medusa Admin at http://localhost:7001
  3. 3API at http://localhost:9000
  4. 4Run migrations: medusa migrations run

Individual Services(5 services)

Copy individual services to mix and match with your existing compose files.

medusa-server
medusa-server:
  build:
    context: .
    dockerfile: Dockerfile
  ports:
    - "9000:9000"
  environment:
    NODE_ENV: development
    DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
    REDIS_URL: redis://redis:6379
    JWT_SECRET: ${JWT_SECRET}
    COOKIE_SECRET: ${COOKIE_SECRET}
    MINIO_ENDPOINT: minio
    MINIO_BUCKET: medusa
    MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
    MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
  depends_on:
    postgres:
      condition: service_healthy
    redis:
      condition: service_started
  networks:
    - medusa-net
  restart: unless-stopped
medusa-admin
medusa-admin:
  image: medusajs/admin:latest
  ports:
    - "7001:80"
  environment:
    MEDUSA_BACKEND_URL: http://medusa-server:9000
  depends_on:
    - medusa-server
  networks:
    - medusa-net
  restart: unless-stopped
postgres
postgres:
  image: postgres:16-alpine
  environment:
    POSTGRES_USER: ${POSTGRES_USER}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    POSTGRES_DB: ${POSTGRES_DB}
  volumes:
    - postgres_data:/var/lib/postgresql/data
  healthcheck:
    test:
      - CMD-SHELL
      - pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
    interval: 10s
    timeout: 5s
    retries: 5
  networks:
    - medusa-net
  restart: unless-stopped
redis
redis:
  image: redis:7-alpine
  volumes:
    - redis_data:/data
  networks:
    - medusa-net
  restart: unless-stopped
minio
minio:
  image: minio/minio:latest
  ports:
    - "9001:9001"
  environment:
    MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
    MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
  volumes:
    - minio_data:/data
  command: server /data --console-address ":9001"
  networks:
    - medusa-net
  restart: unless-stopped

Quick Start

terminal
1# 1. Create the compose file
2cat > docker-compose.yml << 'EOF'
3services:
4 medusa-server:
5 build:
6 context: .
7 dockerfile: Dockerfile
8 ports:
9 - "9000:9000"
10 environment:
11 NODE_ENV: development
12 DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
13 REDIS_URL: redis://redis:6379
14 JWT_SECRET: ${JWT_SECRET}
15 COOKIE_SECRET: ${COOKIE_SECRET}
16 MINIO_ENDPOINT: minio
17 MINIO_BUCKET: medusa
18 MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
19 MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
20 depends_on:
21 postgres:
22 condition: service_healthy
23 redis:
24 condition: service_started
25 networks:
26 - medusa-net
27 restart: unless-stopped
28
29 medusa-admin:
30 image: medusajs/admin:latest
31 ports:
32 - "7001:80"
33 environment:
34 MEDUSA_BACKEND_URL: http://medusa-server:9000
35 depends_on:
36 - medusa-server
37 networks:
38 - medusa-net
39 restart: unless-stopped
40
41 postgres:
42 image: postgres:16-alpine
43 environment:
44 POSTGRES_USER: ${POSTGRES_USER}
45 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
46 POSTGRES_DB: ${POSTGRES_DB}
47 volumes:
48 - postgres_data:/var/lib/postgresql/data
49 healthcheck:
50 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
51 interval: 10s
52 timeout: 5s
53 retries: 5
54 networks:
55 - medusa-net
56 restart: unless-stopped
57
58 redis:
59 image: redis:7-alpine
60 volumes:
61 - redis_data:/data
62 networks:
63 - medusa-net
64 restart: unless-stopped
65
66 minio:
67 image: minio/minio:latest
68 ports:
69 - "9001:9001"
70 environment:
71 MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
72 MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
73 volumes:
74 - minio_data:/data
75 command: server /data --console-address ":9001"
76 networks:
77 - medusa-net
78 restart: unless-stopped
79
80volumes:
81 postgres_data:
82 redis_data:
83 minio_data:
84
85networks:
86 medusa-net:
87 driver: bridge
88EOF
89
90# 2. Create the .env file
91cat > .env << 'EOF'
92# Medusa Configuration
93JWT_SECRET=$(openssl rand -hex 32)
94COOKIE_SECRET=$(openssl rand -hex 32)
95
96# PostgreSQL
97POSTGRES_USER=medusa
98POSTGRES_PASSWORD=secure_postgres_password
99POSTGRES_DB=medusa
100
101# MinIO
102MINIO_ACCESS_KEY=minioadmin
103MINIO_SECRET_KEY=secure_minio_password
104EOF
105
106# 3. Start the services
107docker compose up -d
108
109# 4. View logs
110docker compose logs -f

One-Liner

Run this command to download and set up the recipe in one step:

terminal
1curl -fsSL https://docker.recipes/api/recipes/medusa-ecommerce/run | bash

Troubleshooting

  • Medusa migrations fail with 'relation does not exist': Run 'medusa migrations run' after PostgreSQL is fully initialized and healthy
  • Admin UI shows 'Cannot connect to server': Verify MEDUSA_BACKEND_URL environment variable points to http://medusa-server:9000, not localhost
  • Product image uploads return 403 errors: Check MinIO bucket policy allows public read access and MINIO_ENDPOINT uses service name 'minio'
  • Redis connection timeouts during high traffic: Increase Redis maxmemory setting and configure appropriate eviction policy for session data
  • PostgreSQL connection pool exhausted errors: Adjust Medusa database pool size in medusa-config.js based on expected concurrent users
  • JWT token validation fails after container restart: Ensure JWT_SECRET and COOKIE_SECRET environment variables are persistent across deployments

Community Notes

Loading...
Loading notes...

Download Recipe Kit

Get all files in a ready-to-deploy package

Includes docker-compose.yml, .env template, README, and license

Ad Space