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 应用的绝佳选择,因为它支持长时间运行的进程(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" }
]
}
]
}
warning

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
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 扩展部署

如果你的应用包含扩展(主题应用扩展、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 }}
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 进行会话回放

用于调试商家在你的应用管理 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 });
}
}
info

LogRocket 记录用户会话,让你可以回放商家报告 bug 时的确切体验。这对于调试难以复现的问题非常有价值。注意隐私——配置 LogRocket 以隐藏敏感字段,如支付信息和客户个人身份信息。

部署检查清单

ItemStatusNotes
HTTPS enforcedRequiredAll platforms listed above enforce this
Health check endpointRequiredUsed by hosting platform and uptime monitoring
Environment variables securedRequiredNever in code, always in platform secrets
Database migrations automatedRequiredRun in CI before deployment
Error tracking configuredStrongly recommendedSentry, Bugsnag, or equivalent
Logging structuredStrongly recommendedJSON format for searchability
CI/CD pipelineStrongly recommendedAutomated testing and deployment
Staging environmentRecommendedTest before production
Canary deploymentsRecommendedGradual rollout for risk reduction
Uptime monitoringRecommendedExternal monitoring (Uptime Robot, Better Stack)

坚实的部署流水线是可靠 Shopify 应用的基础。从第一天起就投入时间正确设置它,你将花费更少的时间处理生产问题,更多的时间构建商家喜爱的功能。