How Deployments Work
Understanding the deployment process helps you debug issues and optimize your workflow.
The Deployment Pipeline
When you run slipway slide, here's what happens:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Package │ -> │ Build │ -> │ Push │
│ Source │ │ Image │ │ to Server │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Update │ <- │ Start │ <- │ Stop │
│ Proxy │ │ New │ │ Old │
└──────────────┘ └──────────────┘ └──────────────┘Step-by-Step
1. Package Source Code
$ slipway slide
▶ Packaging source...
→ Creating archive from git (respects .gitignore)
→ Archive size: 2.3 MBSlipway packages your code using git archive:
- Only tracked files are included
.gitignoreis respectednode_modules/is excluded (installed during build)- Untracked files are excluded
2. Upload to Server
▶ Uploading to server...
→ Uploading slipway-myapp-abc123.tar.gz
→ Upload complete (2.3 MB in 1.2s)The archive is uploaded to your Slipway server via HTTPS.
3. Build Docker Image
▶ Building image...
→ docker build -t slipway/myapp:abc123 .
→ Step 1/8: FROM node:22-alpine
→ Step 2/8: WORKDIR /app
→ Step 3/8: COPY package*.json ./
→ Step 4/8: RUN npm ci
→ Step 5/8: COPY . .
→ Step 6/8: RUN npm run build
→ Step 7/8: EXPOSE 1337
→ Step 8/8: CMD ["node", "app.js"]
→ Image built: slipway/myapp:abc123 (245 MB)Slipway builds a Docker image using your Dockerfile:
- Dependencies installed with
npm ci - Assets compiled (if using Shipwright/Vite)
- Image tagged with deployment ID
4. Stop Old Container (Zero-Downtime)
▶ Starting zero-downtime deployment...
→ Starting new container alongside old...For zero-downtime deploys:
- New container starts alongside old
- Health checks run on new container
- Once healthy, traffic switches
- Old container stops
5. Start New Container
▶ Starting new container...
→ docker run -d --name myapp slipway/myapp:abc123
→ Container started: myapp-abc123
→ Waiting for health check...
→ Health check passed ✓The new container:
- Connects to the Slipway network
- Receives environment variables
- Links to database services
- Starts the Sails application
6. Update Proxy Routes
▶ Updating proxy routes...
→ Configuring Caddy for myapp.example.com
→ Route updated ✓Caddy is updated to route traffic to the new container:
- Domain → New container port
- WebSocket support enabled
- SSL termination active
7. Cleanup
▶ Cleaning up...
→ Stopping old container
→ Removing old container
→ Keeping last 10 images for rollback
✓ Deployed myapp (abc123) in 42s
https://myapp.example.comThe Dockerfile
Slipway requires a Dockerfile in your project root. Here's a recommended structure for Sails apps:
Basic Dockerfile
FROM node:22-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --production
# Copy application
COPY . .
# Expose Sails port
EXPOSE 1337
# Start the application
CMD ["node", "app.js"]Production Dockerfile (with build step)
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
# Install all dependencies (including devDependencies)
COPY package*.json ./
RUN npm ci
# Copy source
COPY . .
# Build assets (Shipwright, Vite, etc.)
RUN npm run build
# Production stage
FROM node:22-alpine
WORKDIR /app
# Install tini for proper signal handling
RUN apk add --no-cache tini
# Install production dependencies only
COPY package*.json ./
RUN npm ci --production
# Copy built assets and application
COPY --from=builder /app/.tmp/public ./.tmp/public
COPY --from=builder /app/api ./api
COPY --from=builder /app/config ./config
COPY --from=builder /app/views ./views
COPY --from=builder /app/app.js ./
# Create non-root user
RUN addgroup -g 1001 -S sails && \
adduser -S sails -u 1001 -G sails
USER sails
EXPOSE 1337
# Use tini as init
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "app.js"]Key Dockerfile Best Practices
Use Alpine for smaller images
dockerfileFROM node:22-alpine # ~180MB vs ~1GB for full nodeLeverage layer caching
dockerfileCOPY package*.json ./ RUN npm ci COPY . . # Source changes don't invalidate npm cacheUse tini for signal handling
dockerfileRUN apk add --no-cache tini ENTRYPOINT ["/sbin/tini", "--"]Run as non-root
dockerfileUSER sailsSet NODE_ENV
dockerfileENV NODE_ENV=production
Environment Variables
During deployment, Slipway injects environment variables using a three-tier cascade:
Variable Cascade
Variables are merged in order, with later levels overriding earlier ones:
- Global variables — shared across all apps (set in Settings)
- Environment variables — per environment (set via
slipway env:set) - App variables — per app (multi-app environments only)
Built-in Variables
| Variable | Description |
|---|---|
PORT | Port to listen on (1337) |
NODE_ENV | Environment (production) |
SLIPWAY_APP | App name |
SLIPWAY_ENV | Environment name |
SLIPWAY_DEPLOYMENT_ID | Current deployment ID |
Your Variables
Variables set via slipway env:set are also injected:
DATABASE_URL=postgres://...
SESSION_SECRET=...
STRIPE_KEY=...Linked Services
When you link a database, Slipway sets:
DATABASE_URL=postgres://user:pass@host:5432/db
REDIS_URL=redis://host:6379Health Checks
Slipway checks your app is healthy before routing traffic:
Default Health Check
Slipway checks the root URL returns 200:
curl http://container:1337/Custom Health Check
Configure a dedicated health endpoint:
// api/controllers/health/check.js
module.exports = {
fn: async function () {
// Check database
await User.count()
// Check Redis (if used)
// await sails.helpers.cache.get('health');
return { status: 'healthy', timestamp: new Date() }
}
}Then configure in Slipway:
slipway project:update myapp --health-check-path=/health/checkHealth Check Settings
| Setting | Default | Description |
|---|---|---|
| Path | / | URL to check |
| Timeout | 30s | Max wait for response |
| Interval | 5s | Time between checks |
| Retries | 3 | Attempts before failing |
Zero-Downtime Deployment
Slipway achieves zero-downtime through:
1. Rolling Deployment
Time 0: [Old Container] ← Traffic
Time 1: [Old Container] ← Traffic
[New Container] starting...
Time 2: [Old Container]
[New Container] health check...
Time 3: → [New Container] ← Traffic
[Old Container] stopping...
Time 4: → [New Container] ← Traffic2. Connection Draining
Before stopping the old container:
- Existing connections are allowed to complete
- New connections go to new container
- Grace period: 30 seconds
3. Graceful Shutdown
Sails apps should handle SIGTERM:
// app.js or config/bootstrap.js
process.on('SIGTERM', async () => {
sails.log.info('Received SIGTERM, shutting down gracefully...')
// Close database connections
await sails.lower()
process.exit(0)
})Build Caching
Slipway caches Docker layers for faster builds:
Cached Layers
- Base image (
FROM node:22-alpine) - Dependencies (
npm cilayer)
Cache Invalidation
Cache is invalidated when:
package.jsonorpackage-lock.jsonchanges- Dockerfile changes
- Base image updates
Forcing Fresh Build
slipway slide --no-cacheBuild Logs
View build logs for debugging:
# During deployment
slipway slide # Logs stream automatically
# After deployment
slipway logs myapp --deployment=abc123 --buildCommon Build Errors
npm install fails:
npm ERR! code ERESOLVEFix: Check package-lock.json is committed
Build step fails:
Error: Cannot find module 'vite'Fix: Ensure build tools are in dependencies, not devDependencies
Out of memory:
FATAL ERROR: Heap out of memoryFix: Add --max-old-space-size or use multi-stage build
Deployment History
View past deployments:
slipway deployments myappOutput:
ID STATUS COMMIT MESSAGE DEPLOYED
abc123 running a1b2c3d Fix payment bug 2 minutes ago
def456 stopped e4f5g6h Add feature 2 hours ago
ghi789 stopped i7j8k9l Update deps 1 day agoEach deployment keeps:
- Docker image (for rollback)
- Build logs
- Deploy logs
- Git commit info
Troubleshooting
Deployment Stuck
# Check deployment status
slipway deployment:status myapp
# View logs
slipway logs myapp --deployment=currentContainer Won't Start
# Check container logs
slipway logs myapp
# Common issues:
# - Missing environment variables
# - Database not connected
# - Port already in useHealth Check Failing
# Test health endpoint manually
curl https://myapp.example.com/health/check
# Check app logs
slipway logs myapp -tMulti-App Deployments
Slipway supports running multiple apps in a single environment — for example, a web server and a background worker, or a frontend and API.
Each app in an environment:
- Has its own
Dockerfile(configurable per app, default:Dockerfile) - Runs in its own Docker container with a unique name
- Gets its own deployment record and build/deploy logs
- Receives the merged environment variables plus any app-specific overrides
When you deploy with slipway slide, the default app is deployed. To target a specific app:
slipway slide --app=workerWhen a webhook triggers an auto-deploy, all apps in the environment are deployed — each gets its own container rebuild and deployment record.
See Multi-App Environments for the full guide.
What's Next?
- Learn the Deploy Command options
- View Deployment Logs for debugging
- Set up Auto-Deploy for CI/CD