Skip to main content

應用程式部署

建構出色的 Shopify 應用程式只是一半的挑戰——在生產環境中可靠地部署和運行它是另一半。本模組涵蓋主機託管平台選擇、環境變數管理、使用 GitHub Actions 的 CI/CD 管線設定、監控、日誌和錯誤追蹤。完成後,您將擁有一個生產級的部署管線,讓您對每次發布都充滿信心。

主機託管選項

Shopify 應用程式是需要透過 HTTPS 公開存取的 Web 應用程式。最佳的主機託管平台取決於您應用程式的架構、流量模式和預算。

平台比較

PlatformBest ForStarting CostCold StartsDatabase
VercelRemix/Next.js apps, serverlessFree tierYes (serverless)External required
RailwayFull-stack apps, easy setup$5/monthNoBuilt-in PostgreSQL
RenderContainers, background workersFree tierYes (free tier)Built-in PostgreSQL
Fly.ioLow-latency global appsPay-as-you-goNoBuilt-in PostgreSQL
AWSEnterprise, full controlVariesDepends on serviceRDS, DynamoDB

部署到 Railway

Railway 是 Shopify 應用程式的絕佳選擇,因為它支援長時間運行的程序(webhooks 和背景任務所需),並包含內建的 PostgreSQL:

# Install Railway CLI
npm install -g @railway/cli

# Login and initialize
railway login
railway init

# Add PostgreSQL
railway add --plugin postgresql

# Deploy
railway up

為 Railway 設定您的 Procfile

web: npm run start
worker: npm run worker

部署到 Fly.io

當您需要低延遲的全球分散式部署時,Fly.io 是理想的選擇:

# fly.toml
app = "your-shopify-app"
primary_region = "iad"

[build]
builder = "heroku/buildpacks:20"

[env]
NODE_ENV = "production"
PORT = "8080"

[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1

[[services]]
protocol = "tcp"
internal_port = 8080

[[services.ports]]
port = 80
handlers = ["http"]

[[services.ports]]
port = 443
handlers = ["tls", "http"]

[services.concurrency]
type = "connections"
hard_limit = 250
soft_limit = 200

[[services.http_checks]]
interval = 10000
grace_period = "5s"
method = "get"
path = "/health"
protocol = "http"
timeout = 2000
# Deploy to Fly.io
fly launch
fly deploy

# Scale to multiple regions
fly regions add lhr sin
fly scale count 2

部署到 Vercel

對於基於 Remix 的 Shopify 應用程式,Vercel 提供零設定部署:

// vercel.json
{
"framework": "remix",
"buildCommand": "npm run build",
"installCommand": "npm install",
"regions": ["iad1", "lhr1"],
"functions": {
"api/**/*.js": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/webhooks/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-store" }
]
}
]
}
warning

Vercel 的無伺服器函式有最大執行時間限制(Pro 方案 30 秒,Enterprise 方案 300 秒)。如果您的應用程式需要處理長時間運行的 webhooks 或批次操作,請使用支援持久程序的平台,如 Railway 或 Fly.io,或將繁重工作卸載到獨立的 Worker 服務。

環境變數

必要的環境變數

每個 Shopify 應用程式都需要在生產環境中設定這些環境變數:

# .env.example (never commit actual .env files)

# Shopify App Credentials
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SCOPES=read_products,write_products,read_orders

# App Configuration
HOST=https://your-app.fly.dev
PORT=8080
NODE_ENV=production

# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname

# Session Storage
REDIS_URL=redis://default:pass@host:6379

# Encryption (for access token storage)
ENCRYPTION_KEY=generate-a-32-byte-hex-string

# Error Tracking
SENTRY_DSN=https://key@sentry.io/project-id

# Logging
LOG_LEVEL=info
danger

永遠不要將 .env 檔案提交到版本控制。將 .env* 加入您的 .gitignore。使用您主機託管平台的密鑰管理(Railway 變數、Fly.io secrets、Vercel 環境變數)在執行時注入這些值。

產生安全的加密金鑰

# Generate a 32-byte encryption key for AES-256
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

平台特定的密鑰管理

# Fly.io
fly secrets set SHOPIFY_API_KEY=xxx SHOPIFY_API_SECRET=yyy

# Railway (via dashboard or CLI)
railway variables set SHOPIFY_API_KEY=xxx

# Vercel
vercel env add SHOPIFY_API_KEY production

使用 GitHub Actions 的 CI/CD

正確的 CI/CD 管線確保每次部署都經過一致的測試、建構和部署。

完整的 GitHub Actions 工作流程

# .github/workflows/deploy.yml
name: Test, Build, and Deploy

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
NODE_VERSION: '20'

jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck

unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run test -- --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info

integration-tests:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: shopify_app_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/shopify_app_test
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/shopify_app_test
SHOPIFY_API_KEY: ${{ secrets.TEST_SHOPIFY_API_KEY }}
SHOPIFY_API_SECRET: ${{ secrets.TEST_SHOPIFY_API_SECRET }}

security-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm audit --audit-level=high
- name: Check for secrets in code
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified

deploy-staging:
needs: [lint-and-typecheck, unit-tests, security-audit]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: staging
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --app your-app-staging
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
- name: Run smoke tests against staging
run: |
npm run test:smoke -- --base-url=https://your-app-staging.fly.dev

deploy-production:
needs: [deploy-staging]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://your-app.fly.dev
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --app your-app-production --strategy canary
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
- name: Verify deployment health
run: |
for i in 1 2 3 4 5; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://your-app.fly.dev/health)
if [ "$STATUS" = "200" ]; then
echo "Health check passed"
exit 0
fi
sleep 10
done
echo "Health check failed"
exit 1

Shopify CLI 擴充功能部署

如果您的應用程式包含擴充功能(佈景主題應用程式擴充、結帳 UI 擴充等),請新增一個獨立的部署步驟:

  deploy-extensions:
needs: [deploy-production]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Deploy Shopify extensions
run: npx shopify app deploy --force
env:
SHOPIFY_API_KEY: ${{ secrets.SHOPIFY_API_KEY }}
SHOPIFY_CLI_PARTNERS_TOKEN: ${{ secrets.SHOPIFY_CLI_PARTNERS_TOKEN }}
tip

為生產部署使用具有必要審核者的 GitHub Actions 環境。這在程式碼到達生產環境之前增加了手動核准關卡,讓您的團隊有機會審查預備環境的測試結果。

監控和日誌

健康檢查端點

每個生產應用程式都需要一個健康檢查端點:

// routes/health.js
export async function loader() {
const checks = {};

// Database connectivity
try {
await prisma.$queryRaw`SELECT 1`;
checks.database = 'healthy';
} catch (error) {
checks.database = 'unhealthy';
}

// Redis connectivity
try {
await redis.ping();
checks.redis = 'healthy';
} catch (error) {
checks.redis = 'unhealthy';
}

const allHealthy = Object.values(checks).every((v) => v === 'healthy');

return new Response(JSON.stringify({
status: allHealthy ? 'healthy' : 'degraded',
checks,
timestamp: new Date().toISOString(),
version: process.env.COMMIT_SHA || 'unknown',
}), {
status: allHealthy ? 200 : 503,
headers: { 'Content-Type': 'application/json' },
});
}

結構化日誌

// utils/logger.js
import pino from 'pino';

export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport:
process.env.NODE_ENV === 'development'
? { target: 'pino-pretty' }
: undefined,
base: {
app: 'shopify-app',
env: process.env.NODE_ENV,
version: process.env.COMMIT_SHA,
},
});

// Usage
logger.info({ shop: 'example.myshopify.com', event: 'install' }, 'App installed');
logger.error({ err, shop, webhookTopic }, 'Webhook processing failed');

錯誤追蹤

Sentry 整合

// utils/sentry.js
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
release: process.env.COMMIT_SHA,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.2 : 1.0,
integrations: [
Sentry.prismaIntegration(),
Sentry.httpIntegration(),
],
beforeSend(event) {
// Scrub sensitive data
if (event.request?.headers) {
delete event.request.headers['x-shopify-access-token'];
delete event.request.headers['authorization'];
}
return event;
},
});

export { Sentry };

LogRocket 用於工作階段重播

用於除錯商家回報的應用程式管理介面問題:

// app/entry.client.jsx
import LogRocket from 'logrocket';

if (typeof window !== 'undefined' && process.env.NODE_ENV === 'production') {
LogRocket.init('your-org/your-app');

// Identify the merchant
const shop = new URLSearchParams(window.location.search).get('shop');
if (shop) {
LogRocket.identify(shop, { shop });
}
}
info

LogRocket 記錄使用者工作階段,讓您可以重播商家回報錯誤時的確切體驗。這對於除錯難以重現的問題非常寶貴。請注意隱私——設定 LogRocket 遮蔽敏感欄位,如付款資訊和顧客個人識別資訊。

部署檢查清單

ItemStatusNotes
HTTPS enforced必要All platforms listed above enforce this
Health check endpoint必要Used by hosting platform and uptime monitoring
Environment variables secured必要Never in code, always in platform secrets
Database migrations automated必要Run in CI before deployment
Error tracking configured強烈建議Sentry, Bugsnag, or equivalent
Logging structured強烈建議JSON format for searchability
CI/CD pipeline強烈建議Automated testing and deployment
Staging environment建議Test before production
Canary deployments建議Gradual rollout for risk reduction
Uptime monitoring建議External monitoring (Uptime Robot, Better Stack)

穩固的部署管線是可靠 Shopify 應用程式的基礎。從第一天就投入時間正確設定它,您將花費更少的時間處理生產環境問題,更多的時間建構商家喜愛的功能。