This guide covers deploying POWERBACK.us to a production server with hardened security and proper environment management.
The production deployment uses:
- Systemd for process management
- NGINX for reverse proxy and static file serving
- Secure environment variables with temporary file loading
- SSL certificates for secure connections
- Hardened security with secrets isolation
- Production server with root access
- Domain configured with DNS
- SSL certificate installed
- MongoDB Atlas or local MongoDB instance
- Email service configured
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js 18
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install NGINX
sudo apt install nginx -y# Create user for the application
sudo useradd -m -s /bin/bash fc
sudo usermod -aG sudo fc
# Switch to application user
sudo su - fc# Clone repository
git clone https://github.com/yourusername/powerback.git
cd powerback
# Install dependencies
npm install
# Build client
# Note: The prebuild hook automatically runs scripts/build/build-content.js to generate
# FAQ JSON-LD schema and markdown documentation before building
cd client && npm install && npm run build && cd ..# Switch to production environment
npm run env:switch production
# Or manually create environment file
npm run setup:remote-devCreate a secure secrets directory and environment file:
# Create secure secrets directory
sudo mkdir -p /etc/powerback/secrets
sudo chmod 700 /etc/powerback/secrets
sudo chown root:root /etc/powerback/secrets
# Create environment file with secrets
sudo nano /etc/powerback/secrets/powerback.envAdd your secrets to /etc/powerback/secrets/powerback.env (no REACT_APP_* or other public vars here):
JWT_SECRET=your-actual-jwt-secret
SESSION_SECRET=your-actual-session-secret
MONGODB_URI=your-actual-mongodb-uri
FEC_API_KEY=your-actual-fec-key
# ... other secrets (EMAIL_JONATHAN_PASS, STRIPE keys, etc.)Create /etc/powerback/public.env with all shared (REACTAPP_) and other non-secret config. This file is loaded by the systemd service and is also used at build time for the client. Do not put secrets here. See Environment Management for the full list of REACT*APP*_variables (e.g.REACT_APP_SHARED_DOMAIN, REACT_APP_EMAIL_SUPPORT_USER, REACT_APP_POSITION_PAPER_PATH, REACT_APP_TAGLINE, etc.).
sudo nano /etc/powerback/public.envUse the template from the repo: powerback.service.template (project root). Copy it to the systemd directory and replace placeholders:
sudo cp powerback.service.template /etc/systemd/system/powerback.service
sudo nano /etc/systemd/system/powerback.service # replace {{USER}}, {{GROUP}}, {{NODE_BIN}}, {{APP_SERVER_PATH}}, etc.Placeholders in the template:
| Placeholder | Example |
|---|---|
{{USER}} |
powerback |
{{GROUP}} |
powerback |
{{NODE_BIN}} |
/usr/bin/node |
{{APP_SERVER_PATH}} |
/opt/powerback/app/server.js |
{{SERVER_NAME}} |
vps.powerback.us |
{{STATIC_PUBLIC_DIR}} |
/home/deploy/public_html |
Secrets and other env (PORT, ORIGIN, API keys, etc.) go in /etc/powerback/powerback.env and /etc/powerback/public.env; the service loads both via EnvironmentFile=. See Environment Management.
# Reload systemd configuration
sudo systemctl daemon-reload
# Enable service to start on boot
sudo systemctl enable powerback.service
# Start the service
sudo systemctl start powerback.service
# Check status
sudo systemctl status powerback.serviceCreate /etc/nginx/conf.d/users/fc.conf (for cPanel managed servers):
server {
server_name powerback.us www.powerback.us api.powerback.us;
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# SSL Configuration
ssl_certificate /home/fc/ssl/certs/powerback_us_cert.crt;
ssl_certificate_key /home/fc/ssl/keys/powerback_us_key.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# Force HTTPS
if ($scheme = http) {
return 301 https://$host$request_uri;
}
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
# Proxy position paper PDF to Node.js backend (serves file with clean URL)
location = /position-paper.pdf {
proxy_pass http://127.0.0.1:2512;
proxy_http_version 1.1;
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;
}
# Discord server join link
location = /discord {
return 301 https://discord.gg/tSuTk6R2uG;
}
# API only
location /api/ {
add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always;
proxy_pass http://127.0.0.1:2512;
proxy_http_version 1.1;
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;
}
# Magic-link SPA routes: noindex + always fall back to SPA
location ~ ^/(reset|unsubscribe|join|activate)(/|$) {
add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always;
try_files $uri $uri/ /index.html;
}
# React SPA default
location / {
try_files $uri $uri/ /index.html;
}
# Optional: cache immutable static assets
location ~* \.(js|css|png|webp|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}# Enable site
sudo ln -s /etc/nginx/sites-available/powerback /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload NGINX
sudo systemctl reload nginxEnsure these variables are set in your production environment:
EMAIL_HOST=mail.powerback.us
EMAIL_DOMAIN=powerback.us
EMAIL_JONATHAN_USER=your-email@powerback.us
EMAIL_JONATHAN_PASS=your-email-password
EMAIL_NO_REPLY_PASS=your-no-reply-password
EMAIL_PORT=465If you encounter SSL certificate errors:
// In controller/comms/sendEmail.js
const CONFIG = {
// ... other config
tls: {
rejectUnauthorized: false, // Bypass SSL certificate validation
},
};# Configure UFW firewall
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable# Set proper permissions
sudo chown -R fc:fc /home/fc/powerback
chmod 600 /home/fc/powerback/.env- Never commit
.envfiles to version control - Use the service environment file for production
- Store secrets in secure environment variables
- Use different secrets for development and production
# Check application status
sudo systemctl status powerback.service
# View application logs with colors
powerback-logs
# View recent logs (last 50 lines)
sudo journalctl -u powerback.service -n 50
# View logs from today
sudo journalctl -u powerback.service --since today
# View logs with timestamps
sudo journalctl -u powerback.service -f --no-pager -o short-iso# Deploy with fresh secrets (copies secrets, restarts, deletes temp file)
launch
# Or use the full path
/usr/local/bin/launch-powerback# Check NGINX status
sudo systemctl status nginx
# Test NGINX configuration
sudo nginx -t
# Reload NGINX configuration
sudo systemctl reload nginx
# View NGINX logs
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log# Start the Powerback service
sudo systemctl start powerback.service
# Stop the Powerback service
sudo systemctl stop powerback.service
# Restart the Powerback service
sudo systemctl restart powerback.service
# Enable service to start on boot
sudo systemctl enable powerback.service
# Disable service from starting on boot
sudo systemctl disable powerback.service-
Application not starting:
# Check service status sudo systemctl status powerback.service # Check logs for errors sudo journalctl -u powerback.service --since "5 minutes ago" # Check if environment file exists ls -la /etc/powerback/powerback.env
-
Environment variables not loading:
# Check if secrets file exists ls -la /etc/powerback/secrets/powerback.env # Run deployment script to copy secrets launch # Check if service is running sudo systemctl status powerback.service
-
NGINX 502 errors:
# Check if application is running sudo netstat -tlnp | grep :2512 # Check NGINX configuration sudo nginx -t # Check service status sudo systemctl status powerback.service
-
Email not working:
# Check if environment variables are loaded sudo journalctl -u powerback.service --since "5 minutes ago" | grep -i "environment" # Test email connection node scripts/tests/test-email-templates.js <you@example.com>
-
SSL certificate errors:
- Ensure certificate includes all required domains
- Check certificate expiration
- Use TLS bypass for email if needed
# Restart service with fresh secrets
launch
# Check service status
sudo systemctl status powerback.service
# View live logs
powerback-logs
# Check if port is listening
sudo netstat -tlnp | grep :2512The project uses GitHub Actions for automated CI/CD deployment. The workflow automatically tests, builds, and deploys to production when code is pushed to beta or main branches.
The CI/CD pipeline (.github/workflows/ci_cd.yml) consists of two jobs:
- Test Job: Runs tests, builds the frontend, and performs smoke tests
- Deploy Job: Syncs code to server, deploys frontend, installs dependencies, and restarts services
- GitHub repository with Actions enabled
- SSH access configured on production server
- GitHub Secrets configured (see below)
Configure these secrets in your GitHub repository (Settings → Secrets and variables → Actions):
| Secret Name | Description | Example Value |
|---|---|---|
PROD_SSH_KEY |
Private SSH key for server access | -----BEGIN OPENSSH PRIVATE KEY-----... |
PROD_SSH_KNOWN_HOSTS |
SSH known_hosts entry for server | [hostname] ssh-ed25519 ... |
PROD_SSH_PORT |
SSH port number | 22 |
PROD_SSH_USER |
SSH username | deploy |
PROD_SSH_HOST |
Production server hostname/IP | powerback.us |
PROD_APP_PATH |
Application directory on server | /opt/powerback/app |
PROD_PUBLIC_HTML_PATH |
Public HTML directory (nginx serves from here) | /home/deploy/public_html |
PROD_DEPLOY_LOG_PATH |
Deployment log file path | /var/log/powerback/deploy.log |
PROD_WRAPPER_SCRIPT |
SSH wrapper script path | /home/deploy/bin/gh-actions-wrapper.sh |
PROD_URL |
Production URL for smoke tests | https://powerback.us |
The workflow runs automatically on:
- Push to
betabranch - Push to
mainbranch - Manual trigger via
workflow_dispatch
-
Test Phase:
- Checks out code
- Installs dependencies (root and client)
- Builds frontend with production settings
- Runs smoke tests on build artifacts
- Uploads build artifacts for deploy job
-
Deploy Phase:
- Downloads frontend build artifacts
- Configures SSH with provided keys
- Syncs backend code to
${{ secrets.PROD_APP_PATH }} - Deploys frontend build to
${{ secrets.PROD_PUBLIC_HTML_PATH }} - Installs server dependencies via SSH wrapper (
pbnpminstall) - Restarts services via SSH wrapper (
pbrestart,nginxreload) - Runs post-deploy smoke tests against production URL
The workflow uses restricted SSH commands via a wrapper script (${{ secrets.PROD_WRAPPER_SCRIPT }}). Required commands:
pbnpminstall- Installs npm dependencies in app directorypbrestart- Restarts the powerback servicenginxreload- Reloads nginx configurationpbstatus- (Optional) Checks service statuspbsecurity- (Optional) Security score checkpbwhoami- (Optional) Identity verification
-
SSH Connection Failures:
- Verify
PROD_SSH_KEYis correct and has proper permissions - Check
PROD_SSH_KNOWN_HOSTSmatches server fingerprint - Ensure SSH port is correct in
PROD_SSH_PORT
- Verify
-
Deployment Failures:
- Check if paths in secrets match actual server paths
- Verify wrapper script exists and has execute permissions
- Check server logs:
sudo journalctl -u powerback.service -n 100
-
Build Failures:
- Review test job logs for compilation errors
- Check if all dependencies are properly specified
- Verify Node.js version compatibility
-
Service Not Starting:
- Ensure
pbnpminstallwrapper changes to correct directory (${{ secrets.PROD_APP_PATH }}) - Check systemd service file has correct
WorkingDirectory - Verify dependencies were installed successfully
- Ensure
- GitHub Actions: Automated, runs on push, requires GitHub Secrets setup
- Manual Deployment: Full control, can be run locally, uses local SSH keys
Both methods deploy to the same production server and use the same deployment paths.
# Deploy with fresh secrets (copies secrets, restarts, deletes temp file)
launch
# Or use the full path
/usr/local/bin/launch-powerback# Build client
# Note: The prebuild hook automatically runs scripts/build/build-content.js to generate
# FAQ JSON-LD schema and markdown documentation before building
cd client && npm run build && cd ..
# Copy secrets and restart service
sudo cp /etc/powerback/secrets/powerback.env /etc/powerback/powerback.env
sudo chmod 600 /etc/powerback/powerback.env
sudo chown fc:fc /etc/powerback/powerback.env
sudo systemctl restart powerback.service
# Delete temporary file for security
sudo rm /etc/powerback/powerback.env
# Reload NGINX
sudo systemctl reload nginx# Test API endpoint
curl -I -k https://powerback.us/api/sys/constants
# Test main site
curl -I -k https://powerback.us
# Check if port is listening
sudo netstat -tlnp | grep :2512- Always test in development first
- Use secure environment variable management
- Keep secrets in secure, isolated locations
- Monitor application logs regularly with colored output
- Use systemd for process management
- Configure proper SSL certificates
- Set up monitoring and alerting
- Regular security updates
- Use temporary file loading for secrets
- Delete temporary secret files after loading
For deployment issues:
- Check application logs:
powerback-logs - Check service status:
sudo systemctl status powerback.service - Check NGINX logs:
sudo tail -f /var/log/nginx/error.log - Test API endpoints:
curl -i https://powerback.us/api/sys/constants - Deploy with fresh secrets:
launch
- Deployment Automation - CI/CD pipeline and automated deployment
- Environment Management - Environment configuration
- Production Commands - Production operation commands
- Development Guide - Development setup