Docs / Deployment

Production Deployment

Same pier.yml, real domains, Let's Encrypt certs. Deploy via SSH or CI/CD.

SSH Deploy

The simplest path. Pier SSHs into your server, syncs pier.yml and your build context, then runs pier up remotely.

# First time: set up the remote
pier remote add prod root@your-vps.com

# Deploy
pier deploy prod

  ✓ Syncing pier.yml to root@your-vps.com
  ✓ Building api on remote...
  ✓ Starting services...
  ✓ Health check passed: api → 200 OK
  ✓ Let's Encrypt cert issued for api.yourapp.com
  ✓ Deploy complete (38s)

# Rollback
pier deploy prod --rollback

Production pier.yml

Use pier.prod.yml or environment overrides. The main difference: real domains instead of .dock domains.

pier.prod.yml
name: myapp

services:
  api:
    build: ./api
    port: 3000
    domain: api.yourapp.com    # Real domain → Let's Encrypt
    health: /api/health
    restart: always
    env:
      NODE_ENV: production
      DATABASE_URL: ${SECRET:DATABASE_URL}

  web:
    build: ./web
    port: 3000
    domain: yourapp.com
    depends_on: [api]

  db:
    image: postgres:16
    volumes:
      - pg_data:/var/lib/postgresql/data
    env:
      POSTGRES_PASSWORD: ${SECRET:DB_PASSWORD}

CI/CD Integration

.github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Pier
        run: curl -fsSL https://pier.wejoona.com/install.sh | bash
      - name: Deploy
        run: pier deploy prod
        env:
          PIER_SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          PIER_SECRETS_KEY: ${{ secrets.PIER_SECRETS_KEY }}

Zero-Downtime Deploys

Pier uses rolling restarts by default. For services with health checks, the new container must pass health before the old one is stopped. If health fails, the deploy rolls back automatically. No manual intervention needed.

Back to Docs