Deployment to Shopify Oxygen
Overview
Shopify Oxygen is an edge hosting platform built on Cloudflare Workers. It provides:
- Global edge deployment — Low latency worldwide via Cloudflare's network
- Automatic scaling — No capacity planning needed
- CI/CD integration — Two GitHub Actions workflows deploy automatically on every push
- Environment management — Preview and production environments with variable inheritance
Deployment Architecture
┌─────────────────────────────────────────────────────────────────┐
│ GitHub Repository │
│ │ │
│ git push │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ GitHub Actions (TWO workflows) │ │
│ │ oxygen-deployment-1000042728.yml (Storefront A) │ │
│ │ oxygen-deployment-1000083568.yml (Storefront B) │ │
│ │ │ │
│ │ 1. Checkout code │ │
│ │ 2. Setup Node.js (LTS) │ │
│ │ 3. Cache & install dependencies (npm ci) │ │
│ │ 4. Build & Deploy (shopify hydrogen deploy) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Shopify Oxygen │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Preview │ │ Production │ │ │
│ │ │ Environment │ │ Environment │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘GitHub Actions Workflows
UberLotto uses two Oxygen deployment workflows. Both trigger on every push to any branch, and Shopify Oxygen automatically determines whether to deploy to production (main branch) or a preview environment (other branches).
Workflow 1: Storefront 1000042728
File: .github/workflows/oxygen-deployment-1000042728.yml
name: Storefront 1000042728
on: [push]
permissions:
contents: read
deployments: write
jobs:
deploy:
name: Deploy to Oxygen
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
check-latest: true
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install dependencies
run: npm ci
- name: Build and Publish to Oxygen
run: npx shopify hydrogen deploy
env:
SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN: ${{ secrets.OXYGEN_DEPLOYMENT_TOKEN_1000042728 }}Workflow 2: Storefront 1000083568
File: .github/workflows/oxygen-deployment-1000083568.yml
Identical structure to Workflow 1 but uses its own deployment token:
name: Storefront 1000083568
on: [push]
# ... same steps ...
- name: Build and Publish to Oxygen
run: npx shopify hydrogen deploy
env:
SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN: ${{ secrets.OXYGEN_DEPLOYMENT_TOKEN_1000083568 }}Why Two Workflows?
Each workflow targets a different Shopify storefront environment. This enables deploying the same codebase to multiple storefronts (e.g., staging and production stores, or regional storefronts) from a single repository.
Required GitHub Secrets
Configure these in GitHub Repository → Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
OXYGEN_DEPLOYMENT_TOKEN_1000042728 | Deployment token for Storefront 1000042728 |
OXYGEN_DEPLOYMENT_TOKEN_1000083568 | Deployment token for Storefront 1000083568 |
Getting Deployment Tokens
- Navigate to Shopify Admin → Sales channels → Hydrogen
- Select the target storefront
- Go to Settings → Deployment
- Copy the deployment token
Branch Strategy
| Branch | Environment | URL |
|---|---|---|
main | Production | Custom domain / .myshopify.com |
| Any other branch | Preview | Unique preview URL per deployment |
main (production)
│
├── develop (staging/preview)
│ │
│ ├── feature/xxx
│ └── bugfix/xxx
│
└── hotfix/xxx (urgent fixes)WARNING
Both workflows trigger on every push to any branch. Shopify Oxygen determines the environment (production vs. preview) based on the branch configuration in your Shopify admin.
Manual Deployment
Prerequisites
# Ensure you're logged into Shopify CLI
shopify auth login
# Link to your store (if not already linked)
shopify hydrogen linkDeploy Command
# Build and deploy to Oxygen
npm run build && npx shopify hydrogen deployPreview Deployment
# Deploy to preview environment
npx shopify hydrogen deploy --previewEnvironment Variables in Oxygen
Setting Variables via Shopify Admin
- Shopify Admin → Sales channels → Hydrogen
- Select storefront → Settings → Environment variables
- Add or edit variables
Setting Variables via CLI
# Push local .env to Oxygen
shopify hydrogen env push
# Pull Oxygen env to local
shopify hydrogen env pull
# List current environment variables
shopify hydrogen env listVariable Inheritance
Production Environment
│
▼
┌───────────────────┐
│ Base Variables │ ← Set at storefront level
│ (shared) │
└───────┬───────────┘
│
┌────┴────┐
▼ ▼
┌──────┐ ┌───────┐
│ Prod │ │Preview│ ← Can override base variables
└──────┘ └───────┘Key Environment Variables
The application requires the following environment variables configured in Oxygen:
| Category | Variables |
|---|---|
| Shopify | PUBLIC_STORE_DOMAIN, PUBLIC_STOREFRONT_API_TOKEN, PUBLIC_CHECKOUT_DOMAIN |
| Supabase | SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY |
| Plisio | PLISIO_API_KEY, PLISIO_SECRET_KEY |
| MoonPay | MOONPAY_SECRET_KEY, MOONPAY_WEBHOOK_KEY |
| Analytics | VITE_PUBLIC_POSTHOG_KEY, VITE_PUBLIC_POSTHOG_HOST |
| Cron/Security | CLEANUP_SECRET_TOKEN |
DANGER
Never commit secrets to the repository. Always configure sensitive values through Shopify Admin or shopify hydrogen env push.
Build Process
What Happens During Build
Shopify Codegen — Generates GraphQL types from queries
bashshopify hydrogen codegenVite Build — Bundles the application for production
bashvite buildAsset Processing
- CSS minification (Tailwind CSS v4)
- JavaScript minification and tree-shaking
- PWA manifest generation
- Service worker compilation (via
vite-plugin-pwa)
Build Output
dist/
├── client/
│ ├── assets/ # Hashed static assets
│ ├── manifest.webmanifest # PWA manifest
│ └── sw.js # Service worker
└── worker/
└── index.js # Edge worker bundleMonitoring Deployments
GitHub Actions
- View workflow runs: Repository → Actions tab
- Both workflows run in parallel on each push
- Check deployment status and logs per workflow
- Re-run failed deployments individually
Shopify Admin
- Deployment history: Hydrogen → Deployments
- View preview URLs for non-main branches
- Monitor deployment status in real-time
Rollback Procedure
Via Shopify Admin
- Navigate to Hydrogen → Deployments
- Find the previous successful deployment
- Click "Rollback to this version"
Via Git
# Safe rollback: revert the last commit
git revert HEAD
git push origin mainDANGER
Avoid force-pushing to main. Use git revert to create a new commit that undoes changes, preserving history and enabling both workflows to redeploy the previous state.
Performance Optimization
Edge Caching
Oxygen automatically caches static assets (JS, CSS, images). For dynamic content, set cache headers in loaders:
export async function loader() {
return data(result, {
headers: {
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
},
});
}Bundle Analysis
# Analyze bundle size
npm run build -- --analyzeTroubleshooting
Deployment Timeout
Symptom: Build exceeds the 30-minute timeout.
Solutions:
- Check for infinite loops in build scripts
- Reduce dependency count
- npm caching is already configured in both workflows
Missing Environment Variables
Symptom: Runtime errors about undefined variables.
# Verify variables are set in Oxygen
shopify hydrogen env list
# Push missing variables
shopify hydrogen env pushBuild Failures
Symptom: TypeScript or bundling errors in CI.
# Validate locally before pushing
npm run typecheck
npm run lint
npm run buildPWA Assets Not Found
Symptom: 404 errors for manifest or service worker.
- Verify files exist in
public/directory - Check
vite.config.tsfor correct PWA plugin configuration
Workflow-Specific Failures
If one workflow succeeds but the other fails, check that both deployment tokens are valid and not expired. Each storefront requires its own token.