Self-hosting gives you complete control over your Invoice Generator deployment. This guide covers deployment using Docker and traditional Node.js hosting.
Deployment methods
Docker Recommended for containerized environments
Node.js Direct deployment to VPS or bare metal
Prerequisites
Before deploying:
Node.js 20 or later installed
Turso database created and accessible
Google OAuth credentials configured
Server with at least 512MB RAM
Domain with SSL certificate (recommended)
Docker deployment
Create Dockerfile
Create a Dockerfile in your project root:
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects anonymous telemetry data about general usage.
# Disable by uncommenting the following line:
# ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME= "0.0.0.0"
CMD [ "node" , "server.js" ]
Update Next.js config
Modify next.config.ts to enable standalone output:
const nextConfig : NextConfig = {
output: 'standalone' , // Add this line
turbopack: {
root: "." ,
},
serverExternalPackages: [ "bcryptjs" ],
images: {
remotePatterns: [
{
protocol: "https" ,
hostname: "lh3.googleusercontent.com" ,
},
],
},
};
Build and run container
Build Docker image
docker build -t invoice-generator .
Create environment file
Create .env.production with required variables: TURSO_DATABASE_URL = libsql://your-database.turso.io
TURSO_AUTH_TOKEN = your-auth-token
AUTH_SECRET = your-generated-secret
GOOGLE_CLIENT_ID = your-client-id
GOOGLE_CLIENT_SECRET = your-client-secret
Run database migration
docker run --env-file .env.production invoice-generator \
node scripts/migrate.mjs
This runs scripts/migrate.mjs:1-27 to create database schema from app/lib/schema.sql.
Start container
docker run -d \
--name invoice-generator \
--env-file .env.production \
-p 3000:3000 \
--restart unless-stopped \
invoice-generator
Docker Compose
For easier management, use Docker Compose. Create docker-compose.yml:
version : '3.8'
services :
app :
build : .
ports :
- "3000:3000"
env_file :
- .env.production
restart : unless-stopped
healthcheck :
test : [ "CMD" , "node" , "-e" , "require('http').get('http://localhost:3000')" ]
interval : 30s
timeout : 10s
retries : 3
start_period : 40s
Deploy with:
Node.js deployment
Standard deployment
Clone repository
git clone < your-repositor y >
cd invoice-generator
Install dependencies
npm ci --production=false
Set environment variables
Run database migration
node --env-file=.env.production scripts/migrate.mjs
This creates all tables defined in the schema (app/lib/schema.sql:1-110).
Build application
Build command from package.json:7 runs next build.
Start production server
NODE_ENV = production npm start
Or load environment variables explicitly: node --env-file=.env.production node_modules/next/dist/bin/next start
Process manager (PM2)
Use PM2 for production process management:
Create ecosystem.config.js:
module . exports = {
apps: [{
name: 'invoice-generator' ,
script: 'node_modules/next/dist/bin/next' ,
args: 'start' ,
instances: 'max' ,
exec_mode: 'cluster' ,
env: {
NODE_ENV: 'production' ,
PORT: 3000 ,
},
env_file: '.env.production' ,
error_file: './logs/err.log' ,
out_file: './logs/out.log' ,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z' ,
}],
};
Start with PM2:
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Reverse proxy configuration
Nginx
Configure Nginx as a reverse proxy with SSL:
server {
listen 80 ;
server_name your-domain.com;
return 301 https://$ server_name $ request_uri ;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
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_set_header X-Real-IP $ remote_addr ;
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $ scheme ;
proxy_cache_bypass $ http_upgrade ;
}
# Cache static assets
location /_next/static {
proxy_pass http://localhost:3000;
proxy_cache_valid 60m ;
add_header Cache-Control "public, max-age=3600, immutable" ;
}
}
Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Caddy
Caddy provides automatic HTTPS with Let’s Encrypt:
your-domain.com {
reverse_proxy localhost:3000
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
}
System service (systemd)
Create systemd service for automatic startup:
[Unit]
Description =Invoice Generator
After =network.target
[Service]
Type =simple
User =www-data
WorkingDirectory =/var/www/invoice-generator
EnvironmentFile =/var/www/invoice-generator/.env.production
ExecStart =/usr/bin/node node_modules/next/dist/bin/next start
Restart =always
RestartSec =10
[Install]
WantedBy =multi-user.target
Enable and start:
sudo systemctl enable invoice-generator
sudo systemctl start invoice-generator
sudo systemctl status invoice-generator
Health monitoring
Simple health check
Create a health check endpoint or use Next.js directly:
curl http://localhost:3000
Uptime monitoring
Use tools like:
UptimeRobot - Free tier available
Cronitor - Health checks and alerts
Custom script - Simple cron job with curl
Database considerations
Always use Turso auth tokens in production for secure database access.
The application connects to Turso via app/lib/turso.ts:1-9:
const db = createClient ({
url: process . env . TURSO_DATABASE_URL ! ,
authToken: process . env . TURSO_AUTH_TOKEN ,
});
For optimal performance:
Use Turso edge replicas near your server location
Enable connection pooling if running multiple instances
Monitor database response times
Backup strategy
Implement regular backups:
# Turso provides automatic backups, but you can export manually
turso db shell < your-databas e > '.dump' > backup- $( date +%Y%m%d ) .sql
Schedule with cron:
0 2 * * * /path/to/backup-script.sh
Security hardening
Firewall configuration
# Allow only necessary ports
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
Limit file permissions
chmod 600 .env.production
chown www-data:www-data /var/www/invoice-generator
Enable automatic updates
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Configure OAuth redirect URI
Add your domain to Google OAuth settings: https://your-domain.com/api/auth/callback/google
Troubleshooting
Application won’t start
Check logs:
# Docker
docker logs invoice-generator
# PM2
pm2 logs invoice-generator
# Systemd
sudo journalctl -u invoice-generator -n 50
Common issues:
Missing environment variables - verify .env.production
Port already in use - check with lsof -i :3000
Build artifacts missing - run npm run build again
Database connection fails
Verify connectivity:
# Test database connection
node -e "require('@libsql/client').createClient({url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN}).execute('SELECT 1')"
Check:
TURSO_DATABASE_URL format is correct
TURSO_AUTH_TOKEN is valid and not expired
Server can reach Turso endpoints (check firewall)
High memory usage
Optimize with:
// In ecosystem.config.js for PM2
max_memory_restart : '500M' ,
node_args : '--max-old-space-size=512' ,
Monitoring and logging
Set up centralized logging:
# Install winston for application logging
npm install winston
Monitor system resources:
# CPU and memory
htop
# Disk usage
df -h
# Network connections
netstat -tulpn | grep 3000
Updates and maintenance
Update the application:
git pull origin main
npm ci
npm run build
pm2 restart invoice-generator
# or
sudo systemctl restart invoice-generator
Always test updates in a staging environment before deploying to production.
Next steps
Environment variables Complete reference for all environment variables
Vercel deployment Managed deployment alternative