All checks were successful
Build and Push Docker Images / docker (push) Successful in 1m22s
CRITICAL SECURITY FIXES: - Add webhook secret validation to prevent signature bypass - Implement idempotency protection across all webhook handlers - Add atomic database updates to prevent race conditions - Improve CORS security with origin validation and logging - Remove .env from git tracking to protect secrets STRIPE INTEGRATION: - Add support for checkout.session.expired webhook event - Add Stripe publishable key to environment configuration - Fix webhook handlers with proper idempotency checks - Update Order model with atomic updatePaymentAndStatus method - Add comprehensive logging for webhook processing DEPLOYMENT ARCHITECTURE: - Split into two Docker images (frontend-latest, backend-latest) - Update CI/CD to build separate frontend and backend images - Configure backend on port 3801 (internal 3001) - Add production-ready docker-compose.yml - Remove redundant docker-compose.portainer.yml - Update nginx configuration for both frontend and backend DOCUMENTATION: - Add PRODUCTION-SETUP.md with complete deployment guide - Add docs/stripe-security-fixes.md with security audit details - Add docs/stripe-checkout-sessions.md with integration docs - Add docs/stripe-webhooks.md with webhook configuration - Update .env.example with all required variables including Stripe publishable key CONFIGURATION: - Consolidate to single .env.example template - Update .gitignore to protect all .env variants - Add server/Dockerfile for backend container - Update DEPLOYMENT.md with new architecture 🔒 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
6.2 KiB
6.2 KiB
Production Setup Guide
Quick Start
1. Create Your .env File
Copy the template and fill in your secrets:
cp .env.example .env
Then edit .env with your actual values:
# === Frontend Variables ===
VITE_API_BASE_URL=https://puffinoffset.com/api
VITE_WREN_API_TOKEN=35c025d9-5dbb-404b-85aa-19b09da0578d
VITE_FORMSPREE_CONTACT_ID=xkgovnby
VITE_FORMSPREE_OFFSET_ID=xvgzbory
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51SJc52Pdj1mnVT5k8a2NsdyywF6jlpR2VTAMeHOSoXskOQBNRyKpA35G6sJ2ckgv6UPXq9LbiIspFC6E4Yrppk9m00yAMX8K9Z
# === Backend Variables ===
NODE_ENV=production
PORT=3001
FRONTEND_URL=https://puffinoffset.com
# === Stripe Configuration (Test Mode) ===
STRIPE_SECRET_KEY=sk_test_51SJc52Pdj1mnVT5kkkJQgPpjQPkrf8D6Ik0yvdHgCYHOjZXwdRo3wCMZ4YjqaMDEQL0gyNhUgZZ0sAo4YIGTn6f500Or1vuuxJ
STRIPE_WEBHOOK_SECRET=whsec_6hNtwjRPUvxY3MKOJYfyOZRDZW7mlIsB
# === Wren API Configuration ===
WREN_API_TOKEN=35c025d9-5dbb-404b-85aa-19b09da0578d
WREN_DRY_RUN=true
# === Database Configuration ===
DATABASE_PATH=/app/data/orders.db
2. Deploy with Docker Compose
# Pull latest images
docker compose pull
# Start containers
docker compose up -d
# View logs
docker compose logs -f
# Check status
docker compose ps
3. Configure Nginx on Host
Update your /etc/nginx/sites-available/puffinoffset.com configuration:
# Frontend
server {
listen 443 ssl http2;
server_name puffinoffset.com;
ssl_certificate /etc/letsencrypt/live/puffinoffset.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/puffinoffset.com/privkey.pem;
location / {
proxy_pass http://localhost:3800;
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;
}
}
# Backend API
server {
listen 443 ssl http2;
server_name puffinoffset.com;
ssl_certificate /etc/letsencrypt/live/puffinoffset.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/puffinoffset.com/privkey.pem;
# Stripe webhooks (MUST use raw body)
location /api/webhooks/stripe {
proxy_pass http://localhost:3801;
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;
# CRITICAL: Don't buffer request body for webhook signature verification
proxy_request_buffering off;
proxy_buffering off;
client_max_body_size 10m;
}
# All other API routes
location /api/ {
proxy_pass http://localhost:3801;
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;
}
}
# HTTP redirect
server {
listen 80;
server_name puffinoffset.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
Reload nginx:
sudo nginx -t
sudo systemctl reload nginx
Architecture
Internet
↓
Host Nginx (SSL) :443
↓
├─→ Frontend Container :3800
└─→ Backend Container :3801
Port Mapping:
- Frontend: Host 3800 → Container 3000
- Backend: Host 3801 → Container 3001
Stripe Configuration
Required Stripe Keys:
- Publishable Key (Frontend):
pk_test_*for test mode - Secret Key (Backend):
sk_test_*for test mode - Webhook Secret (Backend):
whsec_*from Stripe Dashboard
Configure Webhooks in Stripe Dashboard:
- Go to: https://dashboard.stripe.com/test/webhooks
- Click "Add endpoint"
- URL:
https://puffinoffset.com/api/webhooks/stripe - Select these events:
checkout.session.completedcheckout.session.async_payment_succeededcheckout.session.async_payment_failedcheckout.session.expired
Test Mode Configuration:
- ✅ Using
sk_test_*andpk_test_*keys - ✅ Using
WREN_DRY_RUN=true(no real offsets purchased) - ✅ Test card: 4242 4242 4242 4242
- ✅ No real charges will occur
Going Live:
When ready for production:
- Get live Stripe keys (
sk_live_*andpk_live_*) - Configure production webhook endpoint
- Set
WREN_DRY_RUN=false - Update
.envwith live keys
Testing
Test Stripe Integration:
# Use test card in checkout:
Card: 4242 4242 4242 4242
Date: Any future date
CVC: Any 3 digits
ZIP: Any 5 digits
Verify Webhooks:
# Check backend logs
docker compose logs -f backend
# Should see:
# ✅ Checkout session completed
# 💳 Payment confirmed
# 🌱 Fulfilling order via Wren API
# ⚠️ DRY RUN MODE: No real offset will be created
Check Health:
curl https://puffinoffset.com/api/health
Troubleshooting
Container won't start:
docker compose logs backend
docker compose logs web
Webhook errors:
- Verify
STRIPE_WEBHOOK_SECRETmatches Stripe Dashboard - Check nginx is not buffering webhook requests
- View webhook attempts in Stripe Dashboard
Database issues:
# Check database volume
docker volume ls
docker volume inspect puffin-app_puffin-data
# Access database
docker compose exec backend sh
sqlite3 /app/data/orders.db
Security Checklist
- ✅
.envexcluded from git - ✅ Webhook signature verification enabled
- ✅ CORS restricted to allowed origins
- ✅ Idempotency protection on webhooks
- ✅ SSL/TLS on host nginx
- ⚠️ Rotate Wren API token (it was previously committed to git)
CI/CD Pipeline
Gitea Actions automatically builds and pushes images on push to main:
- Frontend:
code.puffinoffset.com/matt/puffin-app:frontend-latest - Backend:
code.puffinoffset.com/matt/puffin-app:backend-latest
To deploy updates:
docker compose pull
docker compose up -d
Files Overview
| File | Purpose | In Git? |
|---|---|---|
.env.example |
Template with placeholders | ✅ Yes |
.env |
Your actual secrets | ❌ No (gitignored) |
docker-compose.yml |
Production deployment | ✅ Yes |
nginx-host.conf |
Example nginx config | ✅ Yes |
NEVER commit .env with real secrets!