應用程式部署
建構出色的 Shopify 應用程式只是一半的挑戰——在生產環境中可靠地部署和運行它是另一半。本模組涵蓋主機託管平台選擇、環境變數管理、使用 GitHub Actions 的 CI/CD 管線設定、監控、日誌和錯誤追蹤。完成後,您將擁有一個生產級的部署管線,讓您對每次發布都充滿信心。
主機託管選項
Shopify 應用程式是需要透過 HTTPS 公開存取的 Web 應用程式。最佳的主機託管平台取決於您應用程式的架構、流量模式和預算。
平台比較
| Platform | Best For | Starting Cost | Cold Starts | Database |
|---|---|---|---|---|
| Vercel | Remix/Next.js apps, serverless | Free tier | Yes (serverless) | External required |
| Railway | Full-stack apps, easy setup | $5/month | No | Built-in PostgreSQL |
| Render | Containers, background workers | Free tier | Yes (free tier) | Built-in PostgreSQL |
| Fly.io | Low-latency global apps | Pay-as-you-go | No | Built-in PostgreSQL |
| AWS | Enterprise, full control | Varies | Depends on service | RDS, 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" }
]
}
]
}
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
永遠不要將 .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 }}
為生產部署使用具有必要審核者的 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 });
}
}
LogRocket 記錄使用者工作階段,讓您可以重播商家回報錯誤時的確切體驗。這對於除錯難以重現的問題非常寶貴。請注意隱私——設定 LogRocket 遮蔽敏感欄位,如付款資訊和顧客個人識別資訊。
部署檢查清單
| Item | Status | Notes |
|---|---|---|
| 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 應用程式的基礎。從第一天就投入時間正確設定它,您將花費更少的時間處理生產環境問題,更多的時間建構商家喜愛的功能。