Deployment Guide
Complete guide to deploying your TypeScript Boilerplate application to production.
Pre-Deployment Checklist
Before deploying, ensure:
- [ ] All tests passing (
bun test) - [ ] Environment variables documented in
.env.exemple - [ ] Sensitive data removed from logs
- [ ] Database migrations tested
- [ ] Docker build succeeds locally
- [ ] CORS configured for production domains
- [ ] Error messages use i18n keys (no hardcoded strings)
Deployment Options
Option 1: Docker Compose (VPS)
Best for: Full control, self-hosted on VPS (DigitalOcean, Linode, etc.)
Step 1: Prepare Server
# SSH into your server
ssh user@your-server.com
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Install Docker Compose
sudo apt-get install docker-compose
# Create app directory
mkdir -p /opt/myapp
cd /opt/myappStep 2: Transfer Files
# On your local machine
rsync -avz --exclude 'node_modules' \
--exclude '.git' \
./ user@your-server.com:/opt/myapp/Step 3: Configure Environment
# On server
cd /opt/myapp
cp .env.exemple .env
nano .env # Edit with production valuesStep 4: Deploy
The project uses env_file to load your configurations automatically from .env, while keeping specific overrides in docker-compose.yml for internal Docker networking.
# Build and start all services
podman compose up -d --build
# Check logs
podman compose logs -fStep 5: Database Migrations (Production)
# Run migrations through the app container
podman exec -it app_server bun db:migrate:pushStep 5: Setup Reverse Proxy (Nginx)
# Install Nginx
sudo apt-get install nginx
# Create config
sudo nano /etc/nginx/sites-available/myappserver {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}# Enable site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Setup SSL with Let's Encrypt
sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.comOption 2: Fly.io (Platform-as-a-Service)
Best for: Quick deployment without managing infrastructure
Step 1: Install Fly CLI
curl -L https://fly.io/install.sh | sh
fly auth loginStep 2: Initialize App
fly launch
# Follow prompts:
# - App name: my-typescript-api
# - Region: Choose closest to your users
# - Setup PostgreSQL: Yes
# - Setup Redis: YesStep 3: Configure
Create fly.toml:
app = "my-typescript-api"
primary_region = "gru"
[build]
dockerfile = "Dockerfile"
[env]
NODE_ENV = "production"
PROCESS_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"]Step 4: Set Secrets
fly secrets set DATABASE_URL=$(fly postgres create -n my-db --region gru)
fly secrets set REDIS_URL=$(fly redis create -n my-redis --region gru)Step 5: Deploy
fly deploy
fly logs # Monitor deploymentOption 3: Railway
Best for: Zero-config deployment
Step 1: Install Railway CLI
npm install -g @railway/cli
railway loginStep 2: Initialize
railway init
# Link to existing project or create new
railway linkStep 3: Add Services
From Railway Dashboard:
- Add PostgreSQL database
- Add Redis
- Note connection URLs
Step 4: Configure Variables
railway variables set NODE_ENV=production
railway variables set PROCESS_PORT=3000
# DATABASE_URL and REDIS_URL auto-configuredStep 5: Deploy
railway up
railway logsOption 4: Render
Best for: Free tier, automatic deployments from Git
Step 1: Create Render Account
Visit render.com and connect your GitHub repo
Step 2: Create Services
Create PostgreSQL Database:
- New → PostgreSQL
- Note the Internal Database URL
Create Redis:
- New → Redis
- Note the Internal Redis URL
Create Web Service:
- New → Web Service
- Connect your repo
- Build Command:
bun install && bun db:migrate:push - Start Command:
bun start
Step 3: Configure Environment
In Web Service settings → Environment:
NODE_ENV=production
DATABASE_URL=<from PostgreSQL>
REDIS_URL=<from Redis>
PROCESS_PORT=10000Step 4: Deploy
Render auto-deploys on git push to main
Production Dockerfile
Production Dockerfile
The project already includes a simplified multi-stage production-optimized Dockerfile at the root:
FROM docker.io/oven/bun:latest AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --ignore-scripts
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --production --ignore-scripts
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] tests & build
RUN NODE_ENV=test bun test
ENV NODE_ENV=production
RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install --chown=bun:bun /temp/prod/node_modules node_modules
COPY --from=prerelease --chown=bun:bun /usr/src/app/dist/ dist
COPY --from=prerelease --chown=bun:bun /usr/src/app/package.json .
COPY --from=prerelease --chown=bun:bun /usr/src/app/src/commands/exec-process.ts .
# run the app
USER bun
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "dist/commands/exec-process.js" ]Health Checks
Implement health check endpoint:
// In your routes
api.get("/health", async (request, reply) => {
try {
// Check database
await repository.select().from(user).limit(1);
// Check Redis
await cache.ping();
reply.code(200).send({
status: "healthy",
uptime: process.uptime(),
timestamp: new Date().toISOString()
});
} catch (error) {
reply.code(503).send({
status: "unhealthy",
error: error.message
});
}
});Monitoring
Option 1: PM2 (Built-in)
# View status
pm2 status
# View logs
pm2 logs
# Monitor resources
pm2 monitOption 2: External Services
- Sentry: Error tracking
- Datadog: APM and logs
- New Relic: Performance monitoring
- Uptime Robot: Availability monitoring
Scaling
Horizontal Scaling
# Fly.io: Scale instances
fly scale count 3
# Docker Compose: Use replicas
docker-compose up -d --scale app=3Database Connection Pooling
// Already configured in drizzle.config.ts
pool: {
max: Number(process.env.POSTGRES_POOL) || 20,
min: 5
}Backup Strategy
Database Backups
# Automated backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker exec postgres pg_dump -U user dbname > backup_$DATE.sql
# Upload to S3
aws s3 cp backup_$DATE.sql s3://mybucket/backups/Using Fly.io
fly postgres backup create
fly postgres backup list
fly postgres backup download <backup-id>Rollback Strategy
# Docker: Rollback to previous image
docker tag myapp:latest myapp:previous
docker tag myapp:v1.2.0 myapp:latest
docker-compose up -d
# Fly.io: Rollback deployment
fly releases
fly releases rollback <version>Security Hardening
Use secrets management:
bashfly secrets set API_KEY=xxxConfigure Rate Limiting: The boilerplate uses a custom Redis-based rate limit. You must define these variables in your environment (e.g.,
.envor CI/CD secrets):bashRATE_LIMIT_MAX=100 # Maximum requests per window RATE_LIMIT_WINDOW=60 # Window size in secondsUpdate dependencies regularly:
bashbun updateScan for vulnerabilities:
bashbun audit
Post-Deployment
- [ ] Test all endpoints manually
- [ ] Verify Swagger docs at
/docs - [ ] Monitor error rates
- [ ] Test rollback procedure
- [ ] Document any production-specific configs
Support
For deployment issues:
- Check Troubleshooting Guide
- Open GitHub Issue