Deployment Guide — Virtual Stores System
Version: 1.0 | Last updated: 2026-03-29
Architecture Overview
Internet
|
v
[Cloudflare DNS] -- *.store.inallweb.com -> VPS IP
|
v
[Traefik Reverse Proxy] -- TLS (Let's Encrypt, DNS challenge)
|
+-- /api/* -> Backend (FastAPI, port 8000)
+-- /admin/* -> Admin Panel (React+Vite, port 5173)
+-- /* -> Storefront (Next.js, port 3000)
Infrastructure:
- Hosting: Hetzner Cloud VPS
- DNS: Cloudflare (wildcard
*.store.inallweb.com) - Containers: Docker + Docker Compose
- Reverse Proxy: Traefik v2 with Let's Encrypt
- Database: PostgreSQL 15
- Object Storage: MinIO (S3-compatible)
- Cache: Redis
- CI/CD: GitLab CI (7 stages)
- IaC: Terraform (Hetzner + Cloudflare) + Ansible
Prerequisites
Before deploying, ensure you have:
Credentials & API Keys
| Item | Environment Variable | Required |
|---|---|---|
| Hetzner API token | HCLOUD_TOKEN | Yes |
| Cloudflare API token (Zone.DNS permissions) | CLOUDFLARE_API_TOKEN | Yes |
| SSH key pair | Configured in Hetzner | Yes |
| Stripe LIVE secret key | STRIPE_SECRET_KEY | Yes |
| Stripe LIVE public key | STRIPE_PUBLIC_KEY | Yes |
| Stripe webhook secret | STRIPE_WEBHOOK_SECRET | Yes |
| Stripe Connect webhook secret | STRIPE_CONNECT_WEBHOOK_SECRET | Yes |
| Moloni client ID | MOLONI_CLIENT_ID | For invoicing |
| Moloni client secret | MOLONI_CLIENT_SECRET | For invoicing |
| Zeptomail API key | ZEPTOMAIL_API_KEY | For emails |
| MinIO credentials | MINIO_ACCESS_KEY, MINIO_SECRET_KEY | Yes |
| PostgreSQL credentials | DATABASE_URL | Yes |
| JWT secret key | JWT_SECRET_KEY | Yes |
DNS Configuration
| Record | Type | Value |
|---|---|---|
store.inallweb.com | A | VPS IP address |
*.store.inallweb.com | A | VPS IP address |
Cloudflare must be in DNS-only mode (grey cloud) for Let's Encrypt DNS challenge, or use Cloudflare API token for automatic certificate management.
Email DNS Records (Zeptomail)
| Record | Type | Purpose |
|---|---|---|
| SPF | TXT | Authorize Zeptomail to send |
| DKIM | TXT | Email signing |
| DMARC | TXT | Email policy |
Step 1: Provision Infrastructure
Terraform
The Terraform configuration provisions:
- Hetzner Cloud VPS (Ubuntu)
- Cloudflare DNS records (A record + wildcard)
cd stores-deployment/terraform/
terraform init
terraform plan
terraform apply
Output: VPS IP address.
Ansible Bootstrap
After the VPS is provisioned, run the bootstrap playbook:
cd stores-deployment/ansible/
ansible-playbook -i inventories/prod bootstrap-vps.yml
This installs:
- Docker + Docker Compose
- UFW firewall (ports 22, 80, 443)
- Swap space
- System updates
Step 2: Deploy Application Stack
Docker Compose
The production stack includes:
| Service | Image | Port |
|---|---|---|
stores-backend | FastAPI | 8000 |
stores-admin | React+Vite (nginx) | 80 |
stores-storefront | Next.js | 3000 |
stores-postgres | PostgreSQL 15 | 5432 |
stores-redis | Redis 7 | 6379 |
stores-minio | MinIO | 9000/9001 |
stores-traefik | Traefik v2 | 80/443 |
Deploy
cd stores-deployment/ansible/
ansible-playbook -i inventories/prod deploy-stack.yml
Or manually:
cd stores-management/
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Traefik Configuration
Traefik is configured with:
- Network:
stores-edge(independent from other systems) - Container:
stores-traefik - Certificates: Let's Encrypt via Cloudflare DNS challenge
- Wildcard:
*.store.inallweb.comfor multi-tenant support
Step 3: Database Setup
Initial Schema
The backend automatically creates tables on startup via SQLAlchemy:
# Or manually trigger schema creation
docker exec stores-backend python -c "from src.core.database import engine, Base; Base.metadata.create_all(engine)"
Seed Data
The seed system provisions:
- Plan definitions (Starter, Business, Professional, Enterprise)
- Addon definitions (32 addons)
- Plan-addon inclusions
- Templates
docker exec stores-backend python -m src.seeds.run_all
Database Backup
# Manual backup
docker exec stores-postgres pg_dump -U stores_user stores_db > backup_$(date +%Y%m%d).sql
# Restore
docker exec -i stores-postgres psql -U stores_user stores_db < backup_20260329.sql
Step 4: Post-Deploy Verification
Health Check
curl https://store.inallweb.com/api/health
# Expected: {"status": "healthy", ...}
Register Stripe Webhook
- Go to Stripe Dashboard > Developers > Webhooks
- Add endpoint:
https://store.inallweb.com/api/webhooks/stripe - Select events:
payment_intent.succeededpayment_intent.payment_failedcharge.refundedcharge.dispute.createdcharge.dispute.closedaccount.updated
- Copy the webhook signing secret to
STRIPE_WEBHOOK_SECRET
End-to-End Verification
- Create a test tenant via sysadmin API
- Create a test product
- Place a test order
- Verify payment via Stripe webhook
- Check that email confirmation is sent
- Verify Moloni invoice creation (if configured)
Step 5: Tenant Provisioning
Create First Tenant
curl -X POST https://store.inallweb.com/api/sysadmin/tenants \
-H "Authorization: Bearer $SYSADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "DefenseOps Airsoft",
"slug": "defenseopsairsoft",
"email": "admin@defenseops.pt",
"plan": "business",
"template_id": "tactical"
}'
Response includes admin_email and admin_temp_password.
WooCommerce Migration
For migrating from WooCommerce:
curl -X POST https://store.inallweb.com/api/migration/woocommerce/import \
-H "Authorization: Bearer $SYSADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wc_url": "https://old-store.example.com",
"wc_consumer_key": "ck_...",
"wc_consumer_secret": "cs_..."
}'
Environment Variables Reference
Required
# Database
DATABASE_URL=postgresql://user:pass@host:5432/stores_db
# Auth
JWT_SECRET_KEY=<random-256-bit-key>
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=1440
# Stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLIC_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_CONNECT_WEBHOOK_SECRET=whsec_...
# MinIO
MINIO_ENDPOINT=minio:9000
MINIO_ACCESS_KEY=...
MINIO_SECRET_KEY=...
MINIO_BUCKET=stores
MINIO_USE_SSL=false
# Redis
REDIS_URL=redis://redis:6379/0
Optional
# Email (Zeptomail)
ZEPTOMAIL_API_KEY=...
ZEPTOMAIL_FROM_EMAIL=noreply@store.inallweb.com
ZEPTOMAIL_FROM_NAME=Loja Online
# Moloni (fiscal invoicing)
MOLONI_CLIENT_ID=...
MOLONI_CLIENT_SECRET=...
# CDN
CDN_BASE_URL=https://cdn.store.inallweb.com
# E2E Testing
SKIP_RATE_LIMIT_FOR_E2E=false
CI/CD Pipeline (GitLab)
The pipeline has 7 stages:
| Stage | Description |
|---|---|
| lint | ESLint, Ruff, type checking |
| test | pytest (backend), unit tests (frontend) |
| build | Docker image build |
| e2e | Playwright E2E tests |
| push | Push images to GitLab Container Registry |
| deploy | Ansible deploy to target environment |
| verify | Health check and smoke tests |
Environments
| Environment | Branch | URL |
|---|---|---|
| dev | any feature branch | *.dev.store.inallweb.com |
| uat | develop | *.uat.store.inallweb.com |
| staging | staging | *.staging.store.inallweb.com |
| production | main | *.store.inallweb.com |
Rollback Procedure
Quick Rollback (Blue-Green)
# Activate the other deployment slot
./release-activate.sh prod manifest.json store.inallweb.com <other-slot>
Manual Rollback
# 1. Identify the last working image tag
docker images | grep stores-backend
# 2. Update docker-compose with the previous tag
# 3. Redeploy
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 4. Verify
curl https://store.inallweb.com/api/health
Database Rollback
For database schema changes, always use reversible migrations:
# Restore from backup
docker exec -i stores-postgres psql -U stores_user stores_db < backup_YYYYMMDD.sql
Monitoring
Logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f stores-backend
# Last 100 lines
docker compose logs --tail=100 stores-backend
Resource Usage
docker stats
Disk Usage
# Docker disk usage
docker system df
# MinIO storage
docker exec stores-minio mc ls local/stores/
Security Checklist
- All secrets in environment variables (never hardcoded)
- UFW firewall enabled (ports 22, 80, 443 only)
- HTTPS enforced via Traefik (automatic redirect)
- JWT tokens with expiration
- Password hashing with bcrypt
- Rate limiting enabled (disable for E2E with
SKIP_RATE_LIMIT_FOR_E2E) - CORS configured for allowed origins
- Stripe webhook signature verification enabled
- Row-level security via tenant_id on all queries
- Audit logging enabled for admin actions
- GDPR compliance: data export and account deletion endpoints