This guide covers syncing the Next.js frontend and Flask backend to an EC2 instance, running them under systemd, and using nginx (with existing Let’s Encrypt certs) so HTTPS on port 443 serves the site while the app listens on port 3000 (frontend) and port 5050 (backend).

Host: cat.rickyjuicy.com
SSH user: ubuntu
Remote app root: /home/ubuntu/cat/
- Frontend: /home/ubuntu/cat/frontend/
- Backend: /home/ubuntu/cat/backend/

Sync code from your machine

Place your private key next to the script (e.g. priv_cat.pem), then:

cd "/path/to/Cat Project Final"
chmod +x deploy-to-ec2.sh
./deploy-to-ec2.sh

Override defaults if needed:

REMOTE_HOST=cat.rickyjuicy.com REMOTE_USER=ubuntu SSH_KEY=./priv_cat.pem ./deploy-to-ec2.sh

The script uses rsync and excludes node_modules/, .next/, venv/, __pycache__/, .git/, and logs/ (rebuild these on the server).
Do not rely on rsync for secrets: create .env files on the server manually or with a one-off scp.


1. One-time server setup (Ubuntu)

SSH in:

ssh -i ./priv_cat.pem ubuntu@cat.rickyjuicy.com

Node.js (LTS) and npm

Example using NodeSource (Node 20.x):

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v && npm -v

Alternatively use nvm.

Python 3 and venv

sudo apt-get update
sudo apt-get install -y python3 python3-venv python3-pip

Backend virtualenv and dependencies

cd /home/ubuntu/cat/backend
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install gunicorn

gunicorn is used for production systemd (recommended instead of python run.py with debug mode).

Frontend install and production build

On the server, create the production env file before building (Next.js bakes NEXT_PUBLIC_* at build time):

nano /home/ubuntu/cat/frontend/.env.production.local

Minimal content:

NEXT_PUBLIC_API_BASE=https://cat.rickyjuicy.com

Then:

cd /home/ubuntu/cat/frontend
npm ci
npm run build

Backend environment file

Create /home/ubuntu/cat/backend/.env (do not commit real values). Follow back_web-4/README.md for keys. Important for production:

FLASK_ENV=production
PORT=5050
CORS_ORIGINS=https://cat.rickyjuicy.com
SECRET_KEY=your-secret-key
JWT_SECRET=JHuttjhgjht7^6jhgkjh
SQLITE_PATH=/home/ubuntu/cat/backend/data.db
UPLOAD_DIR=/home/ubuntu/cat/backend/uploads
POE_API_KEY=...
OPENROUTER_API_KEY=...

Adjust paths if you store the database or uploads elsewhere.


2. Running frontend and backend

Quick manual test (no systemd)

Backend (bind to localhost only if nginx is the only public entry; use 127.0.0.1 with gunicorn):

cd /home/ubuntu/cat/backend
source venv/bin/activate
gunicorn -w 2 -b 127.0.0.1:5050 run:app

Frontend:

cd /home/ubuntu/cat/frontend
npm start

Next.js defaults to port 3000. For a manual test you can use PORT=3000 npm start.

Production: systemd units

Create two unit files.

/etc/systemd/system/pawmatch-backend.service

[Unit]
Description=PawMatch Flask API (gunicorn)
After=network.target

[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/cat/backend
Environment="PATH=/home/ubuntu/cat/backend/venv/bin"
ExecStart=/home/ubuntu/cat/backend/venv/bin/gunicorn -w 2 -b 127.0.0.1:5050 run:app
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

/etc/systemd/system/pawmatch-frontend.service

[Unit]
Description=PawMatch Next.js (production)
After=network.target

[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/cat/frontend
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/npm start
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Note: If npm is not at /usr/bin/npm, run which npm on the server and set ExecStart to the full path, or use ExecStart=/bin/bash -lc 'cd /home/ubuntu/cat/frontend && npm start'.

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now pawmatch-backend.service
sudo systemctl enable --now pawmatch-frontend.service
sudo systemctl status pawmatch-backend.service
sudo systemctl status pawmatch-frontend.service

After each deploy, restart if needed:

sudo systemctl restart pawmatch-backend.service
sudo systemctl restart pawmatch-frontend.service

Optional: run Flask without gunicorn

python run.py runs the dev server with debug=True in run.py; not ideal for production. Prefer gunicorn as above.


3. Nginx: HTTPS (443) → Next.js (3000), API → Flask (5050)

Browsers must call the API on the same origin as the page (https://cat.rickyjuicy.com). The frontend uses NEXT_PUBLIC_API_BASE (e.g. https://cat.rickyjuicy.com); nginx routes:

  • /http://127.0.0.1:3000 (Next.js)
  • /api, /uploads, /healthhttp://127.0.0.1:5050 (Flask)

Assume Let’s Encrypt already issued a certificate for cat.rickyjuicy.com (paths under /etc/letsencrypt/live/cat.rickyjuicy.com/).

Create or edit a site file, e.g. /etc/nginx/sites-available/cat.rickyjuicy.com:

# HTTP → HTTPS (adjust if certbot already manages this file)
server {
    listen 80;
    server_name cat.rickyjuicy.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name cat.rickyjuicy.com;

    ssl_certificate     /etc/letsencrypt/live/cat.rickyjuicy.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cat.rickyjuicy.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    client_max_body_size 20M;

    # Flask API and static uploads
    location /api/ {
        proxy_pass http://127.0.0.1:5050;
        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;
    }

    location /uploads/ {
        proxy_pass http://127.0.0.1:5050;
        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;
    }

    location /health {
        proxy_pass http://127.0.0.1:5050;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Next.js (everything else)
    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Enable the site and reload:

sudo ln -sf /etc/nginx/sites-available/cat.rickyjuicy.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

If certbot already installed a full server { ... } block for this domain, merge the location blocks into that block instead of duplicating listen 443.


4. Post-deploy checklist

Check Action
Security group Open 22 (SSH), 80 (HTTP), 443 (HTTPS). Do not expose 3000 or 5050 publicly if nginx proxies locally.
Firewall (UFW) sudo ufw allow OpenSSH and sudo ufw allow 'Nginx Full'; enable if you use UFW.
HTTPS curl -I https://cat.rickyjuicy.com → expect 200 or 3xx.
Health curl -s https://cat.rickyjuicy.com/health{ "ok": true }
Frontend Open https://cat.rickyjuicy.com in a browser.
After code change On server: npm ci && npm run build in frontend/; restart both systemd services; nginx reload only if config changed.

5. Typical deploy flow

  1. Run ./deploy-to-ec2.sh from your laptop.
  2. On EC2: update backend venv if requirements.txt changed (pip install -r requirements.txt).
  3. Update frontend .env.production.local if API URL changed.
  4. cd frontend && npm ci && npm run build.
  5. sudo systemctl restart pawmatch-backend.service pawmatch-frontend.service.
  6. sudo nginx -t && sudo systemctl reload nginx if nginx config changed.

Reference

Component Path / port
Deploy script deploy-to-ec2.sh (project root)
Backend entry backend/run.py, gunicorn run:app
Flask routes /api/..., uploads /uploads/..., health /health
Frontend Next.js npm start (default port 3000)