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,/health→http://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
- Run
./deploy-to-ec2.shfrom your laptop. - On EC2: update backend venv if
requirements.txtchanged (pip install -r requirements.txt). - Update frontend
.env.production.localif API URL changed. cd frontend && npm ci && npm run build.sudo systemctl restart pawmatch-backend.service pawmatch-frontend.service.sudo nginx -t && sudo systemctl reload nginxif 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) |