DocsDeveloper SectionRun & Deploy Project (Docker)

Phased Docker Deployment & Developer Documentation

This comprehensive guide provides step-by-step instructions for transferring files and running all project microservices—including infrastructure, Strapi CMS, Medusa 2.0, Storefront (Next.js), and OpenObserve observability—on a Virtual Private Server (VPS) or local VM using Docker.

Due to heavy resource consumption during Next.js and Strapi builds, the deployment process is designed to be phased (10 controlled phases) to prevent server crashes caused by CPU or memory exhaustion.

1. Prerequisites

Ensure the following tools are installed on your VPS or VM:

  • Docker and Docker Compose (v2) (Quick install for Ubuntu/Debian: curl -fsSL https://get.docker.com | sudo sh)
  • Git (if cloning the repository directly)

Docker permission (docker.sock):

On a fresh VPS or VM, docker compose may fail with permission denied while trying to connect to the docker API at unix:///var/run/docker.sock. This is normal — the install script adds only root by default. Fix once per user:

Docker permission (docker.sock):
bash
sudo usermod -aG docker $USER
newgrp docker # or log out and SSH back in
docker ps # should work without sudo

2. Transferring Files to the VPS

Before configuring GitHub Actions, you must get the code onto your Linux server (VPS). There are two main methods:

Method 1: Using Git (Recommended)

Public Repository:

Git Clone (Public)
bash
git clone https://github.com/<YOUR_GITHUB_USERNAME>/<YOUR_REPOSITORY_NAME>.git
cd <YOUR_REPOSITORY_NAME>

Private Repository (Using SSH Key - Recommended):

  • 1. Generate SSH Key on VPS: ssh-keygen -t ed25519 -C "your_email@example.com"
  • 2. Display & Copy Public Key: cat ~/.ssh/id_ed25519.pub
  • 3. Add Key to GitHub: Open GitHub > Settings > SSH and GPG keys > New SSH key (Title: VPS Server).
  • 4. Clone via SSH on VPS:
Git Clone via SSH
bash
git clone git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_REPOSITORY_NAME>.git
cd <YOUR_REPOSITORY_NAME>

Alternative for Private Repository (Personal Access Token - PAT with 'repo' scope):

Git Clone via PAT
bash
git clone https://<YOUR_GITHUB_USERNAME>:<PERSONAL_ACCESS_TOKEN>@github.com/<YOUR_GITHUB_USERNAME>/<YOUR_REPOSITORY_NAME>.git
cd <YOUR_REPOSITORY_NAME>

Method 2: Direct Transfer via SCP / Rsync

If you are on Linux/Mac, run this command in your local machine terminal:

Rsync to Server
bash
rsync -avz --exclude 'node_modules' --exclude '.git' --exclude '.next' ./ user@your_vps_ip:/root/next-strapi-medusa

⚡ 3. Quick Start: Interactive Setup Wizard (Recommended)

Instead of manually running each phase, execute the interactive setup wizard on your VPS. It guides you step-by-step through profile selection (A/B/C), automatically generates all cryptographic secrets, configures .env, starts services in the correct sequence, polls for container health, extracts Meilisearch keys, creates admin accounts, and seeds sample data:

VPS Interactive Setup Wizard
bash
chmod +x server-setup.sh
bash server-setup.sh
Pro Tip
If you prefer manual step-by-step execution or need to debug a specific service, follow the manual phased deployment guide below.

4. Choose a Deployment Profile & Configure .env

Once you are in the project folder on your server, copy the template and edit it:
cp .env.example .env && nano .env

Before running any phase, pick one profile. Public URLs are baked into Strapi, Medusa, and the Storefront at build time — choosing the wrong profile early means extra rebuilds later.

Do you need a domain or Cloudflare?
No — not for a local VM or LAN test. You can run the full stack on a VM IP. Cloudflare is only for exposing a real public domain on the internet.
Recommended for Testing, Portfolio Demos & Local VMs

Best for portfolio demos and local testing. Uses Caddy inside Docker (no separate install on the VM) with self-signed certificates and .local hostnames. Medusa Admin works in production mode without cookie hacks.

Profile B Checklist (in this order):

  • 1. Before any docker compose up: Set .env to Profile B URLs below
  • 2. Phase 1: Start infrastructure (postgres, redis, meilisearch, minio, minio-setup)
  • 3. Phase 2: Start Caddy (docker compose up -d caddy) — immediately after Phase 1, before Strapi/Medusa
  • 4. On Windows Host: Add VM IP to C:\Windows\System32\drivers\etc\hosts:
    192.168.1.103 store.local api.local admin.local search.local analytics.local monitor.local
  • 5. Phase 3+: Build & run Strapi, Medusa, Storefront, Umami & OpenObserve as usual
.env (Profile B Snippet - Recommended)
env
# Profile B: VM / LAN HTTPS (.local hostnames) - Recommended ★
STOREFRONT_PUBLIC_URL=https://store.local
MEDUSA_BACKEND_PUBLIC_URL=https://api.local
STRAPI_PUBLIC_URL=https://admin.local
MEILISEARCH_PUBLIC_URL=https://search.local
MINIO_PUBLIC_URL=https://api.local/minio/mystore
GOOGLE_CALLBACK_URL=https://store.local/ir/account/auth/callback
STOREFRONT_URL=http://storefront:8000
MEDUSA_NODE_ENV=production
MEDUSA_ALLOW_HTTP_COOKIES=false
CLOUDFLARE_TUNNEL_TOKEN=
Admin URLs (Profile B):
• Medusa: https://api.local/app
• Strapi: https://admin.local/admin
• Storefront: https://store.local
• Meilisearch: https://search.local
• Umami: https://analytics.local
• OpenObserve: https://monitor.local

🛡️ Cloudflare DNS & Production Edge Hardening Guide

1. Before Running Setup (DNS Pre-configuration):

In Cloudflare dashboard (DNS > Records), create A records pointing to your VPS IP for: @ (root domain), www, api, admin, search, analytics, and monitor.

Crucial: Set Cloudflare proxy status to DNS Only (Gray Cloud) during initial setup so Caddy can automatically complete the ACME HTTP-01 / TLS-ALPN challenge to issue free SSL certificates.

2. After Services are Running (Post-Deployment Hardening):

SSL/TLS Encryption Mode: Set to Full (Strict).
Edge Certificates: Enable Always Use HTTPS, Automatic HTTPS Rewrites, and set Minimum TLS Version to TLS 1.2 (or 1.3).
DNS Proxying: Switch all DNS records from gray to Proxied (Orange Cloud) for CDN acceleration and origin DDoS masking.
Security / WAF: Enable Bot Fight Mode.
Network: Ensure WebSockets and gRPC are enabled.
Caching: Under Caching > Configuration, verify Browser Cache TTL is set to Respect Existing Headers to preserve instant dynamic cart and inventory updates.

HTTP Basic Auth & Caddy Edge Protection:

Strapi Admin Panel (admin.yourdomain.com/admin* and /): Protected with HTTP Basic Auth. Public API routes (/api/*) and uploaded media (/uploads/*) remain open so Storefront and Medusa can fetch content freely without 401 errors.
OpenObserve Dashboard (monitor.yourdomain.com): Protected with HTTP Basic Auth for dual-layer edge defense alongside internal OpenObserve credentials.

Generate a secure bcrypt hash for BASIC_AUTH_HASH in .env with:

Generate Caddy Bcrypt Password Hash
bash
docker run --rm caddy:latest caddy hash-password --plaintext "your_secret_password"

Unified URL Reference Table

ServiceProfile A (HTTP + IP)Profile B (Local HTTPS)Profile C (Production)
Storefronthttp://<VM_IP>:8000https://store.localhttps://yourdomain.com
Medusa Adminhttp://<VM_IP>:9000/apphttps://api.local/apphttps://api.yourdomain.com/app
Strapi Adminhttp://<VM_IP>:1337/adminhttps://admin.local/adminhttps://admin.yourdomain.com/admin
Umami Analyticshttp://<VM_IP>:3005https://analytics.localhttps://analytics.yourdomain.com
OpenObservehttp://<VM_IP>:5080https://monitor.localhttps://monitor.yourdomain.com
Meilisearch127.0.0.1:7700https://search.localhttps://search.yourdomain.com

What each Public URL means in .env:

  • STOREFRONT_PUBLIC_URL: Next.js shop (port 8000 inside Docker)
  • MEDUSA_BACKEND_PUBLIC_URL: Medusa API + Admin API (/app)
  • STRAPI_PUBLIC_URL: Strapi CMS (/admin); preview links use STOREFRONT_PUBLIC_URL
  • MEILISEARCH_PUBLIC_URL: Public search URL for client-side queries (Profile A: http://127.0.0.1:7700, Profile B: https://search.local, Profile C: https://search.yourdomain.com)
  • MINIO_PUBLIC_URL: Public prefix for media (Profile A: http://IP:9001/mystore, Profile B/C: https://api.*/minio/mystore)
  • STOREFRONT_URL: Internal only (http://storefront:8000) for Medusa -> Storefront cache revalidation

Set these BEFORE Phase 1:

Secrets (openssl rand -hex 32): POSTGRES_PASSWORD, MEILI_MASTER_KEY, all STRAPI_* secrets, MEDUSA_JWT_SECRET, MEDUSA_COOKIE_SECRET, REVALIDATE_SECRET, UMAMI_APP_SECRET.
Admin logins: MEDUSA_ADMIN_EMAIL, MEDUSA_ADMIN_PASSWORD, MINIO_ROOT_USER, MINIO_ROOT_PASSWORD, UMAMI_USERNAME, UMAMI_PASSWORD.
Public URLs: Pick Profile A, B, or C above.

Leave empty until the matching phase:

• Phase 4: STRAPI_API_TOKEN_FOR_MEDUSA, STRAPI_API_TOKEN_FOR_FRONT
• Phase 6: MEDUSA_PUBLISHABLE_KEY
• Phase 7: MEILI_SEARCH_KEY
• Phase 8: UMAMI_WEBSITE_ID

Medusa Admin rule: Always keep MEDUSA_NODE_ENV=production in Docker. Never set development — the admin UI is production-built and dev mode causes a white screen.

5. Initial GitHub Builds & Docker Login

Before starting the server setup, you must build the backend images on GitHub Actions so your server can download them:

  1. 1. Go to the Actions tab in your GitHub repository and enable workflows.
  2. 2. Select Build and Push Strapi Image and click Run workflow.
  3. 3. Select Build and Push Medusa Image and click Run workflow.
  4. 4. (Private repo) Generate a GitHub Personal Access Token (Classic) with scopes repo, workflow, and read:packages, then log in to GitHub Container Registry on your VPS:
VPS Docker Registry Login
bash
echo "<YOUR_PAT>" | docker login ghcr.io -u <YOUR_GITHUB_USERNAME> --password-stdin

Phase Overview (At a Glance)

PhaseWhat runsProfile AProfile B / C
Phase 1Postgres, Redis, MinIO, Meilisearch
Phase 2Caddy (Docker) + hosts on Profile B⏭ Skip✅ Right after Phase 1, before Strapi
Phase 3Strapi CMS (Pull from GHCR)
Phase 4Strapi API tokens -> .env
Phase 5Medusa backend (Pull from GHCR)
Phase 6Medusa publishable key -> .env
Phase 7Meilisearch search-only key -> .env
Phase 8Umami Analytics (Before Storefront)Optional
Phase 9Storefront Next.js (GitHub Actions Build & Deploy)
Phase 10Observability (OpenObserve, Vector, cAdvisor)Optional✅ Recommended

Phase 1: Running Infrastructure

Start only the database and core infrastructure services. Data persists in Docker volumes:

Phase 1 - Infrastructure
bash
docker compose up -d postgres redis meilisearch minio minio-setup

Wait until all services are healthy: docker compose ps

Phase 2: HTTPS Gateway (Caddy) & Edge Security

Profile A: Skip this phase entirely.
Profile B / C: Run this immediately after Phase 1 and before building Strapi or Medusa.

Caddy runs as a lightweight Docker container providing automatic SSL certificates, HSTS/CSP security headers, and HTTP Basic Auth edge defense.

Phase 2 - Start Caddy
bash
docker compose up -d caddy
docker compose logs -f caddy

Profile B Windows hosts file config:

Open C:\Windows\System32\drivers\etc\hosts as Administrator and add:
192.168.1.103 store.local api.local admin.local analytics.local monitor.local
(Replace 192.168.1.103 with your VM IP. Do not add Meilisearch — it stays internal).

Phase 3: Pulling and Running Strapi CMS

Once infrastructure (and Caddy, if Profile B/C) is ready, pull the pre-built image from GitHub Packages and start it:

Phase 3 - Pull & Run Strapi
bash
docker compose pull strapi
docker compose up -d strapi
docker compose logs -f strapi

Wait until the server listens on port 1337 (1–3 minutes on first start). Open Strapi Admin (Profile B: https://admin.local/admin, Profile A: http://<VM_IP>:1337/admin) and register your admin account.

Phase 4: Generating Strapi API Tokens

Medusa and Storefront need API tokens before they can read Strapi data:

  1. 1. Open Strapi Admin > Settings > API Tokens.
  2. 2. For Medusa (STRAPI_API_TOKEN_FOR_MEDUSA): Click the Full Access token, click Regenerate, and copy into STRAPI_API_TOKEN_FOR_MEDUSA in .env (or paste into server-setup.sh).
  3. 3. For Storefront (STRAPI_API_TOKEN_FOR_FRONT): Click the Read-Only token, click Regenerate, and copy into STRAPI_API_TOKEN_FOR_FRONT in .env.

Phase 5: Pulling and Running Medusa Backend

Pull the pre-built Medusa image from GitHub Container Registry and start it:

Phase 5 - Pull & Run Medusa
bash
docker compose pull medusa
docker compose up -d medusa
docker compose logs -f medusa

Automatic first-run steps: db:migrate (2–5+ min) -> creates admin user -> API on port 9000. Open Medusa Admin at https://api.local/app (Profile B) or http://<VM_IP>:9000/app (Profile A). Log in with MEDUSA_ADMIN_EMAIL and MEDUSA_ADMIN_PASSWORD.

Phase 6: Generating Publishable API Key in Medusa

Create a Publishable API Key in Medusa Admin and attach it to your sales channel:

  1. 1. Log in to Medusa Admin (/app) > Settings > API Keys > Create Key.
  2. 2. Name: Storefront -> Save -> Copy the pk_... key into MEDUSA_PUBLISHABLE_KEY in .env (or paste into server-setup.sh).
  3. 3. CRITICAL: Open the key > Sales Channels tab > attach to Default Sales Channel. Without this, products won't appear on the storefront.

Phase 7: Retrieving Meilisearch Search API Key

The storefront uses a restricted search-only key for client instant-search, never MEILI_MASTER_KEY. Querying keys directly from server localhost:

Phase 7 - Query Keys
bash
curl -H "Authorization: Bearer <MEILI_MASTER_KEY>" http://127.0.0.1:7700/keys

Copy the Default Search API Key and set it as MEILI_SEARCH_KEY in .env (the wizard extracts this automatically).

Phase 8: Umami Analytics (Before Storefront)

Next.js needs the Umami Website ID at compile time during the GitHub Actions build:

Phase 8 - Start Umami
bash
docker compose up -d umami

Open Umami (Profile B: https://analytics.local, Profile A: http://<VM_IP>:3005). Log in (admin / umami), go to Settings > Websites > Add website, copy Website ID into UMAMI_WEBSITE_ID in .env.

Phase 9: GitHub Actions Setup & Building the Storefront

Configure GitHub Secrets and the Self-Hosted Runner on your VPS to automatically build and deploy the Storefront:

Step 1: Configure GitHub Secrets (production environment)

In your GitHub repo, go to Settings > Environments > New environment ('production') and click Add environment secret to create the following secrets:

1. STOREFRONT_BUILD_ARGS (Compile-Time Variables)

Contains your Next.js build variables. Copy the template above and fill in the keys generated in previous phases.

STOREFRONT_BUILD_ARGS Template
env
MEDUSA_BACKEND_URL=http://medusa:9000
NEXT_PUBLIC_MEDUSA_BACKEND_URL=https://api.yourdomain.com
NEXT_PUBLIC_STRAPI_URL=https://admin.yourdomain.com
NEXT_PUBLIC_BASE_URL=https://store.yourdomain.com
NEXT_PUBLIC_MEILISEARCH_HOST=https://search.yourdomain.com
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_...
NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY=...
NEXT_PUBLIC_UMAMI_WEBSITE_ID=...
NEXT_PUBLIC_DEFAULT_REGION=US
DEFAULT_LOCALE=en-US
NEXT_PUBLIC_MEILISEARCH_INDEX_NAME=products
NEXT_PUBLIC_ENABLE_IRAN_FEATURES=false

2. REGISTRY_URL

URL to the component registry. Default: public Loomix Blocks registry:
https://raw.githubusercontent.com/landa33/loom-blocks-registry/main/src/modules

3. GITHUB_PAT (Optional)

GitHub Personal Access Token (Classic). Requires repo, workflow, and read:packages scopes. Used for GHCR container downloads, CI/CD triggering, and private component registry access.

4. DEPLOY_PATH

The absolute path to the project directory on your Linux VPS (e.g. /root/loomix-commerce). Required for the Self-Hosted Runner to locate the Docker compose project.

Step 2: Install Self-Hosted Runner on VPS

In GitHub repo > Settings > Actions > Runners > New self-hosted runner (Linux). Run the setup commands on your server, then run sudo ./svc.sh install && sudo ./svc.sh start to run it permanently as a background service.

Step 3: Build Storefront Image

Go to GitHub Actions tab > select Build and Push Storefront Image > click Run workflow.

Step 4: Run Storefront on VPS

Phase 9 - Run Storefront
bash
docker compose pull storefront
docker compose up -d storefront

Phase 10: Observability & Monitoring (OpenObserve)

A lightweight, production-grade observability and logging stack built on OpenObserve, Vector, cAdvisor, and docker-stats-exporter:

Phase 10 - Start Observability Stack
bash
docker compose up -d openobserve vector cadvisor docker-stats-exporter

OpenObserve Dashboard:

Open https://monitor.yourdomain.com (Profile C) or https://monitor.local (Profile B) or http://<VM_IP>:5080 (Profile A). Log in with your OpenObserve root credentials.

Collected Telemetry Streams:

docker_logs: Unified container logs and instant error search across all microservices.

caddy_access: Live HTTP web traffic, IP addresses, HTTP status codes (2xx/4xx/5xx), latencies, and request paths streamed directly from Caddy JSON logs via Vector.

docker_stats: Real-time per-container memory usage %, CPU %, and network I/O.

host_metrics: Server-wide CPU load, RAM utilization, and disk I/O metrics.

Telegram Alert Notifications:

In OpenObserve dashboard, navigate to Reliability > Destinations to attach a Telegram bot webhook (https://api.telegram.org/bot<TOKEN>/sendMessage), then create alert rules under Reliability > Alerts for instant real-time alerting.

Storefront Settings & Component Syncing (Strapi → GitHub CI/CD)

This project features a fully automated Git-backed synchronization architecture between the Strapi Admin Panel and the Next.js Storefront:

1. Storefront Settings Preset Sync (storefront-settings.json)

In Strapi under Storefront Settings, editors customize product card styles (card-1, card-2, card-3), header and footer layouts, page templates, theme color palettes, and global preferences.

Development Mode (NODE_ENV=development): Strapi writes directly to store/src/lib/config/storefront-settings.json on local disk for instant Next.js hot-reload.
Production Mode (NODE_ENV=production): Strapi commits the JSON preset directly to the GitHub repository via GitHub Contents API. This commit triggers build-storefront.yml and deploy-storefront.yml to automatically build and deploy the updated container via the Self-Hosted Runner with zero manual downtime.

2. Loomix Block Component Sync (sync-loom-component.js)

In Strapi Storefront Management, clicking + Add to Storefront (Queue) or Remove Style (Queue) stages component changes into sync-history.json. Clicking Update Storefront automatically synchronizes settings presets and dispatches the component updates to GitHub Actions to build and deploy the updated storefront.

Production Sync Environment Variables:

Place these variables in website-admin/.env and the server .env. Ensure the token has 'repo' and 'workflow' scopes:

GitHub CI/CD Sync Variables
env
GITHUB_DEPLOY_TOKEN=ghp_your_personal_access_token # Requires 'repo' and 'workflow' scopes
GITHUB_DEPLOY_REPO=your_username/your_repo_name

Switching Profiles Mid-Deployment

Already ran Phase 1 with Profile A and want Profile B? Phase 1 data is safe in Docker volumes — only app containers need updating.

What you changeWhat to restart
Public URLs in .envStrapi, Medusa, Storefront (trigger GitHub Action build first, then pull)
MEDUSA_ALLOW_HTTP_COOKIESMedusa only
Add Caddy (A -> B)Start Caddy (docker compose up -d caddy); pull new images
hosts file (Profile B)Nothing on the VM — edit Windows hosts only
Profile Switch Example
bash
# 1. Edit .env — switch to Profile B URLs, set MEDUSA_ALLOW_HTTP_COOKIES=false
nano .env
# 2. Add hosts entry on Windows
# 3. Start Caddy
docker compose up -d caddy
# 4. Pull new images:
docker compose pull strapi medusa && docker compose up -d strapi medusa

Updating the Server (Push / Pull)

All three apps (Strapi, Medusa, Storefront) are built via their GitHub Actions workflows.

For Strapi and Medusa, update the server with:
docker compose pull && docker compose up -d

For the Storefront, pushing code or syncing styles automatically builds and deploys to the server via the Self-Hosted Runner.

Sample Data Seeding Script (Local Development)

For local development or quick testing with already-running containers, use setup.sh (Linux/Mac) or setup.bat (Windows). This populates Medusa and Strapi with 30 sample products, categories, collections, pages, blogs, and translations.

Run Setup Script
bash
./setup.sh # On Linux / macOS
.\setup.bat # On Windows

Network Architecture Reference

Direct port access (Profile A, or debugging):

  • Storefront: :8000
  • Medusa API + Admin: :9000
  • Strapi CMS: :1337
  • Meilisearch: :7700
  • MinIO Console: :9001
  • Umami Analytics: :3005
  • OpenObserve: :5080

Caddy hostnames (Profile B):

  • https://store.local -> storefront:8000
  • https://api.local -> medusa:9000
  • https://admin.local -> strapi:1337
  • https://analytics.local -> umami:3000
  • https://monitor.local -> openobserve:5080
  • https://api.local/minio/ -> minio:9000

Internal Docker networking:

Containers talk to each other by service name (e.g. http://storefront:8000, http://medusa:9000, http://meilisearch:7700). Meilisearch is internal-only and must never be exposed publicly.

Useful Docker Commands

Container status
bash
docker compose ps
Live logs
bash
docker compose logs -f medusa
docker compose logs -f strapi
docker compose logs -f storefront
docker compose logs -f openobserve
Reclaim disk space
bash
docker builder prune -f
docker system prune -f
Full restart (volumes preserved)
bash
docker compose down
docker compose up -d