应用部署
构建出色的 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 应用的绝佳选择,因为它支持长时间运行的进程(Webhook 和后台任务需要)并包含内置的 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
Configure your Procfile for Railway:
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 秒)。如果你的应用需要处理长时间运行的 Webhook 或批量操作,请使用支持持久进程的平台(如 Railway 或 Fly.io),或将繁重的工作卸载到单独的工作服务。
环境变量
必要的环境变量
每个 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 扩展部署
如果你的应用包含扩展(主题应用扩展、Checkout 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 进行会话回放
用于调试商家在你的应用管理 UI 中报告的问题:
// 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 记录用户会话,让你可以回放商家报告 bug 时的确切体验。这对于调试难以复现的问题非常有价值。注意隐私——配置 LogRocket 以隐藏敏感字段,如支付信息和客户个人身份信息。
部署检查清单
| Item | Status | Notes |
|---|---|---|
| HTTPS enforced | Required | All platforms listed above enforce this |
| Health check endpoint | Required | Used by hosting platform and uptime monitoring |
| Environment variables secured | Required | Never in code, always in platform secrets |
| Database migrations automated | Required | Run in CI before deployment |
| Error tracking configured | Strongly recommended | Sentry, Bugsnag, or equivalent |
| Logging structured | Strongly recommended | JSON format for searchability |
| CI/CD pipeline | Strongly recommended | Automated testing and deployment |
| Staging environment | Recommended | Test before production |
| Canary deployments | Recommended | Gradual rollout for risk reduction |
| Uptime monitoring | Recommended | External monitoring (Uptime Robot, Better Stack) |
坚实的部署流水线是可靠 Shopify 应用的基础。从第一天起就投入时间正确设置它,你将花费更少的时间处理生产问题,更多的时间构建商家喜爱的功能。