This guide covers deploying Wardrobe AI Closet to production.
Create app/Dockerfile:
FROM oven/bun:1.3 AS base
WORKDIR /app
# Install dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
# Copy source
COPY . .
# Generate Prisma Client
RUN bunx prisma generate
# Build Next.js
RUN bun run build
# Production image
FROM oven/bun:1.3-slim
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
# Copy built app
COPY --from=base /app/.next/standalone ./
COPY --from=base /app/.next/static ./.next/static
COPY --from=base /app/public ./public
COPY --from=base /app/prisma ./prisma
COPY --from=base /app/node_modules/.prisma ./node_modules/.prisma
EXPOSE 3000
CMD ["bun", "run", "server.js"]
version: '3.8'
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: wardrobe
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: wardrobe
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wardrobe"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
app:
build:
context: ./app
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://wardrobe:${POSTGRES_PASSWORD}@postgres:5432/wardrobe
REDIS_URL: redis://redis:6379
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
DATA_DIR: /data
volumes:
- app_data:/data
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
postgres_data:
redis_data:
app_data:
POSTGRES_PASSWORD=your_secure_password
OPENROUTER_API_KEY=your_openrouter_key
# Build and start
docker-compose up -d
# Run migrations
docker-compose exec app bunx prisma migrate deploy
# Check logs
docker-compose logs -f app
# Access at http://localhost:3000
# Pull latest code
git pull
# Rebuild and restart
docker-compose up -d --build
# Run new migrations
docker-compose exec app bunx prisma migrate deploy
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Install Bun
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib
# Install Redis
sudo apt install -y redis-server
# Install Nginx (for reverse proxy)
sudo apt install -y nginx certbot python3-certbot-nginx
# Create database user and database
sudo -u postgres psql
CREATE USER wardrobe WITH PASSWORD 'your_secure_password';
CREATE DATABASE wardrobe OWNER wardrobe;
\q
# Create app directory
sudo mkdir -p /var/www/wardrobe
sudo chown $USER:$USER /var/www/wardrobe
# Clone repository
cd /var/www/wardrobe
git clone https://github.com/yourusername/ADHD-Closet.git .
# Install dependencies
cd app
bun install
# Setup environment
cp .env.example .env
nano .env # Edit with your values
# /var/www/wardrobe/app/.env
DATABASE_URL="postgresql://wardrobe:your_secure_password@localhost:5432/wardrobe"
REDIS_URL="redis://localhost:6379"
OPENROUTER_API_KEY="your_openrouter_key"
DATA_DIR="/var/www/wardrobe/data"
NODE_ENV="production"
PORT="3000"
# Generate Prisma client
bunx prisma generate
# Run migrations
bunx prisma migrate deploy
# Build app
bun run build
# Test run
bun run start
# Ctrl+C to stop
Create /etc/systemd/system/wardrobe.service:
[Unit]
Description=Wardrobe AI Closet
After=network.target postgresql.service redis.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/wardrobe/app
Environment="NODE_ENV=production"
EnvironmentFile=/var/www/wardrobe/app/.env
ExecStart=/home/YOUR_USER/.bun/bin/bun run start
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
# Set permissions
sudo chown -R www-data:www-data /var/www/wardrobe
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable wardrobe
sudo systemctl start wardrobe
sudo systemctl status wardrobe
Create /etc/nginx/sites-available/wardrobe:
server {
listen 80;
server_name your-domain.com;
client_max_body_size 50M;
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;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/wardrobe /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Pros: Easy deployment, automatic SSL, global CDN Cons: Requires PostgreSQL external host (Neon, Supabase, etc.)
# Install Vercel CLI
npm i -g vercel
# Deploy
cd app
vercel
# Set environment variables in Vercel dashboard
# - DATABASE_URL
# - REDIS_URL (Upstash)
# - OPENROUTER_API_KEY
# - DATA_DIR=/tmp (note: ephemeral!)
Note: Vercel’s filesystem is read-only. Use S3 or Cloudflare R2 for images.
Pros: Simple, includes PostgreSQL and Redis Cons: Paid only
# Install Railway CLI
npm i -g @railway/cli
# Login
railway login
# Create project
railway init
# Add services
railway add --database postgres
railway add --database redis
# Deploy
railway up
# Set environment variables in dashboard
Pros: Full control, persistent volumes, global deployment Cons: More complex setup
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Create app
fly launch
# Add PostgreSQL
fly postgres create
# Add Redis
fly redis create
# Deploy
fly deploy
# Set secrets
fly secrets set OPENROUTER_API_KEY=xxx
DATABASE_URL: PostgreSQL connection stringREDIS_URL: Redis connection string (for BullMQ)OPENROUTER_API_KEY: OpenRouter API key for AI featuresDATA_DIR (default: ./data): Directory for storing imagesPORT (default: 3000): Server portNODE_ENV (default: development): Environment modeOPENROUTER_IMAGE_MODEL: Model for image generationOPENROUTER_VISION_MODEL: Model for image inferenceOPENROUTER_TEXT_MODEL: Model for outfit generation# For Nginx
sudo certbot --nginx -d your-domain.com
# Auto-renewal is configured automatically
sudo certbot renew --dry-run
# Create backup script: /var/www/wardrobe/backup.sh
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/wardrobe"
mkdir -p $BACKUP_DIR
# Backup PostgreSQL
pg_dump -U wardrobe wardrobe | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# Backup images
tar -czf $BACKUP_DIR/images_$DATE.tar.gz /var/www/wardrobe/data/images
# Keep only last 30 days
find $BACKUP_DIR -type f -mtime +30 -delete
# Make executable
chmod +x /var/www/wardrobe/backup.sh
# Add to crontab (daily at 2 AM)
crontab -e
0 2 * * * /var/www/wardrobe/backup.sh
Use the built-in export feature:
# Install rclone for cloud backups
curl https://rclone.org/install.sh | sudo bash
# Configure cloud storage
rclone config
# Sync backups to cloud
rclone sync /var/backups/wardrobe remote:wardrobe-backups
# App status
sudo systemctl status wardrobe
# Database status
sudo systemctl status postgresql
# Redis status
sudo systemctl status redis
# Nginx status
sudo systemctl status nginx
# App logs
sudo journalctl -u wardrobe -f
# Nginx logs
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
# Check disk usage
df -h
# Check data directory size
du -sh /var/www/wardrobe/data
See TROUBLESHOOTING.md for common issues and solutions.
The app works in three ways:
When installed as a PWA, you get:
For developers wanting to build native iOS/Android apps:
Web PWA is fine for most users. Build native if you need:
cd app
npm install
npx cap add android
npx cap sync android
npm run build:android
APK will be at: app/android/app/build/outputs/apk/debug/app-debug.apk
For detailed instructions, CI/CD setup, and troubleshooting, see the Android Build Guide.
cd app
npm install
npx cap add ios
npx cap sync ios
npx cap open ios
Then build and sign in Xcode.
| Feature | Web Browser | PWA | Native App |
|---|---|---|---|
| No install | ✅ | ❌ | ❌ |
| App icon | ❌ | ✅ | ✅ |
| Offline | ❌ | ✅ | ✅ |
| NFC (iOS) | ❌ | ❌ | ✅ |
| NFC (Android) | ✅ | ✅ | ✅ |
| App stores | ❌ | ❌ | ✅ |
PWA won’t install
Native build fails
npx cap doctor to diagnose issuesapp/README.mdFor technical implementation details, see app/README.md section on Capacitor setup.