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:
sudo usermod -aG docker $USERnewgrp docker # or log out and SSH back indocker ps # should work without sudo2. 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 https://github.com/<YOUR_GITHUB_USERNAME>/<YOUR_REPOSITORY_NAME>.gitcd <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 git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_REPOSITORY_NAME>.gitcd <YOUR_REPOSITORY_NAME>Alternative for Private Repository (Personal Access Token - PAT with 'repo' scope):
git clone https://<YOUR_GITHUB_USERNAME>:<PERSONAL_ACCESS_TOKEN>@github.com/<YOUR_GITHUB_USERNAME>/<YOUR_REPOSITORY_NAME>.gitcd <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 -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:
chmod +x server-setup.shbash server-setup.shPro Tip
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?
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
# Profile B: VM / LAN HTTPS (.local hostnames) - Recommended ★ STOREFRONT_PUBLIC_URL=https://store.localMEDUSA_BACKEND_PUBLIC_URL=https://api.localSTRAPI_PUBLIC_URL=https://admin.localMEILISEARCH_PUBLIC_URL=https://search.localMINIO_PUBLIC_URL=https://api.local/minio/mystoreGOOGLE_CALLBACK_URL=https://store.local/ir/account/auth/callback STOREFRONT_URL=http://storefront:8000MEDUSA_NODE_ENV=productionMEDUSA_ALLOW_HTTP_COOKIES=falseCLOUDFLARE_TUNNEL_TOKEN=• 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:
docker run --rm caddy:latest caddy hash-password --plaintext "your_secret_password"Unified URL Reference Table
| Service | Profile A (HTTP + IP) | Profile B (Local HTTPS) | Profile C (Production) |
|---|---|---|---|
| Storefront | http://<VM_IP>:8000 | https://store.local | https://yourdomain.com |
| Medusa Admin | http://<VM_IP>:9000/app | https://api.local/app | https://api.yourdomain.com/app |
| Strapi Admin | http://<VM_IP>:1337/admin | https://admin.local/admin | https://admin.yourdomain.com/admin |
| Umami Analytics | http://<VM_IP>:3005 | https://analytics.local | https://analytics.yourdomain.com |
| OpenObserve | http://<VM_IP>:5080 | https://monitor.local | https://monitor.yourdomain.com |
| Meilisearch | 127.0.0.1:7700 | https://search.local | https://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_URLMEILISEARCH_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. Go to the Actions tab in your GitHub repository and enable workflows.
- 2. Select Build and Push Strapi Image and click Run workflow.
- 3. Select Build and Push Medusa Image and click Run workflow.
- 4. (Private repo) Generate a GitHub Personal Access Token (Classic) with scopes
repo,workflow, andread:packages, then log in to GitHub Container Registry on your VPS:
echo "<YOUR_PAT>" | docker login ghcr.io -u <YOUR_GITHUB_USERNAME> --password-stdinPhase Overview (At a Glance)
| Phase | What runs | Profile A | Profile B / C |
|---|---|---|---|
| Phase 1 | Postgres, Redis, MinIO, Meilisearch | ✅ | ✅ |
| Phase 2 | Caddy (Docker) + hosts on Profile B | ⏭ Skip | ✅ Right after Phase 1, before Strapi |
| Phase 3 | Strapi CMS (Pull from GHCR) | ✅ | ✅ |
| Phase 4 | Strapi API tokens -> .env | ✅ | ✅ |
| Phase 5 | Medusa backend (Pull from GHCR) | ✅ | ✅ |
| Phase 6 | Medusa publishable key -> .env | ✅ | ✅ |
| Phase 7 | Meilisearch search-only key -> .env | ✅ | ✅ |
| Phase 8 | Umami Analytics (Before Storefront) | Optional | ✅ |
| Phase 9 | Storefront Next.js (GitHub Actions Build & Deploy) | ✅ | ✅ |
| Phase 10 | Observability (OpenObserve, Vector, cAdvisor) | Optional | ✅ Recommended |
Phase 1: Running Infrastructure
Start only the database and core infrastructure services. Data persists in Docker volumes:
docker compose up -d postgres redis meilisearch minio minio-setupWait 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.
docker compose up -d caddydocker compose logs -f caddyProfile 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:
docker compose pull strapidocker compose up -d strapidocker compose logs -f strapiWait 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. Open Strapi Admin > Settings > API Tokens.
- 2. For Medusa (
STRAPI_API_TOKEN_FOR_MEDUSA): Click theFull Accesstoken, click Regenerate, and copy intoSTRAPI_API_TOKEN_FOR_MEDUSAin.env(or paste intoserver-setup.sh). - 3. For Storefront (
STRAPI_API_TOKEN_FOR_FRONT): Click theRead-Onlytoken, click Regenerate, and copy intoSTRAPI_API_TOKEN_FOR_FRONTin.env.
Phase 5: Pulling and Running Medusa Backend
Pull the pre-built Medusa image from GitHub Container Registry and start it:
docker compose pull medusadocker compose up -d medusadocker compose logs -f medusaAutomatic 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. Log in to Medusa Admin (/app) > Settings > API Keys > Create Key.
- 2. Name:
Storefront-> Save -> Copy thepk_...key intoMEDUSA_PUBLISHABLE_KEYin.env(or paste intoserver-setup.sh). - 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:
curl -H "Authorization: Bearer <MEILI_MASTER_KEY>" http://127.0.0.1:7700/keysCopy 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:
docker compose up -d umamiOpen 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.
MEDUSA_BACKEND_URL=http://medusa:9000NEXT_PUBLIC_MEDUSA_BACKEND_URL=https://api.yourdomain.comNEXT_PUBLIC_STRAPI_URL=https://admin.yourdomain.comNEXT_PUBLIC_BASE_URL=https://store.yourdomain.comNEXT_PUBLIC_MEILISEARCH_HOST=https://search.yourdomain.comNEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_...NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY=...NEXT_PUBLIC_UMAMI_WEBSITE_ID=...NEXT_PUBLIC_DEFAULT_REGION=USDEFAULT_LOCALE=en-USNEXT_PUBLIC_MEILISEARCH_INDEX_NAME=productsNEXT_PUBLIC_ENABLE_IRAN_FEATURES=false2. 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
docker compose pull storefrontdocker compose up -d storefrontPhase 10: Observability & Monitoring (OpenObserve)
A lightweight, production-grade observability and logging stack built on OpenObserve, Vector, cAdvisor, and docker-stats-exporter:
docker compose up -d openobserve vector cadvisor docker-stats-exporterOpenObserve 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_DEPLOY_TOKEN=ghp_your_personal_access_token # Requires 'repo' and 'workflow' scopesGITHUB_DEPLOY_REPO=your_username/your_repo_nameSwitching 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 change | What to restart |
|---|---|
| Public URLs in .env | Strapi, Medusa, Storefront (trigger GitHub Action build first, then pull) |
| MEDUSA_ALLOW_HTTP_COOKIES | Medusa 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 |
# 1. Edit .env — switch to Profile B URLs, set MEDUSA_ALLOW_HTTP_COOKIES=falsenano .env # 2. Add hosts entry on Windows # 3. Start Caddydocker compose up -d caddy # 4. Pull new images:docker compose pull strapi medusa && docker compose up -d strapi medusaUpdating 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.
./setup.sh # On Linux / macOS.\setup.bat # On WindowsNetwork 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
docker compose psdocker compose logs -f medusadocker compose logs -f strapidocker compose logs -f storefrontdocker compose logs -f openobservedocker builder prune -fdocker system prune -fdocker compose downdocker compose up -d