Files
DailyMeals/docs/DEPLOYMENT.md
2026-06-04 06:24:56 +02:00

246 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deploying DailyMeals on a Linux server
This guide assumes a **VPS or dedicated Linux host** (Ubuntu 22.04/24.04 or Debian 12) with:
- Docker Engine + Docker Compose plugin
- An **external PostgreSQL** database (already populated — no DB container in this stack)
- A domain name pointing at the server (recommended for HTTPS)
## Architecture on the server
```
Internet
[ports 80 / 443] nginx (frontend container) ── SPA static files
└── /api ──► ASP.NET Core API (api container, internal :5000)
External PostgreSQL (your DB host)
```
Only **80** and **443** need to be public. The API stays on the Docker network.
---
## 1. Prepare the server
```bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Docker (official convenience script) + Compose plugin
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
# Log out and back in so the docker group applies
docker --version
docker compose version
```
Optional firewall (UFW):
```bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
---
## 2. Allow the API to reach PostgreSQL
On your **database server** (or cloud DB console):
1. Ensure PostgreSQL listens on an address reachable from the app server.
2. Add the **app servers public IP** to `pg_hba.conf` (or the providers “allowed IPs” list).
3. Open port **5432** only to that IP (security group / firewall).
Test from the app server (requires `postgresql-client`):
```bash
psql "host=YOUR_DB_HOST port=5432 dbname=YOUR_DB user=YOUR_USER password=YOUR_PASS" -c "SELECT 1"
```
---
## 3. Deploy the application
```bash
# Clone (or upload) the project
git clone <your-repo-url> dailymeals
cd dailymeals
# Configure secrets
cp .env.example .env
nano .env # or vim
```
### Required `.env` values (production example)
```env
DB_CONNECTION_STRING=Host=10.0.0.5;Port=5432;Database=mealplan;Username=mealapp;Password=STRONG_DB_PASSWORD
JWT_SECRET=PASTE_OUTPUT_OF_openssl_rand_-base64_48
JWT_ISSUER=DailyMeals
JWT_AUDIENCE=DailyMeals
# Must match the URL users type in the browser (scheme + host, no trailing slash)
CORS_ALLOWED_ORIGIN=https://meals.example.com
```
Generate JWT secret:
```bash
openssl rand -base64 48
```
Build and start (production overlay hides public API port 5000):
```bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
```
Check status:
```bash
docker compose ps
docker compose logs -f api
docker compose logs -f frontend
curl -k https://localhost/health # via nginx proxy: use /api path — health is on API
curl -k https://localhost/api/../health # wrong — health is NOT proxied by default
```
API health is internal. Verify the site loads in a browser: `https://your-server-ip` (self-signed cert until step 4).
---
## 4. DNS and HTTPS (Lets Encrypt)
1. Create an **A record**: `meals.example.com` → your servers public IP.
2. Wait for DNS to propagate.
### Option A — Certificates on the host, mounted into Docker (recommended)
Install Certbot:
```bash
sudo apt install -y certbot
```
Stop the frontend container so port 80 is free:
```bash
cd ~/dailymeals
docker compose -f docker-compose.yml -f docker-compose.prod.yml stop frontend
```
Obtain certificate:
```bash
sudo certbot certonly --standalone -d meals.example.com --agree-tos -m you@example.com
```
Copy and edit the TLS compose file:
```bash
cp docker-compose.tls.example.yml docker-compose.tls.yml
nano docker-compose.tls.yml # set your domain paths under /etc/letsencrypt/live/...
```
Ensure `.env` has:
```env
CORS_ALLOWED_ORIGIN=https://meals.example.com
```
Start again:
```bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.tls.yml up -d
```
Renewal (add to crontab, renew then reload):
```bash
sudo certbot renew --deploy-hook "cd /home/YOUR_USER/dailymeals && docker compose -f docker-compose.yml -f docker-compose.prod.yml restart frontend"
```
### Option B — Keep self-signed (testing only)
Skip `TLS_*` in `.env`. The image generates a self-signed cert. Browsers will show a security warning.
---
## 5. Post-deploy checklist
| Check | Command / action |
|--------|------------------|
| Containers running | `docker compose ps` |
| API connects to DB | `docker compose logs api` — no connection errors on startup |
| Login / register | Open `https://meals.example.com/register` |
| HTTPS valid | Padlock in browser (with Lets Encrypt) |
| Firewall | Only 22, 80, 443 (and 5432 **not** open to the world on app server) |
---
## 6. Updates (new version)
```bash
cd ~/dailymeals
git pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
```
---
## 7. Important production notes
### Refresh tokens are in-memory
The API stores refresh tokens **in RAM**. After a container restart, users must log in again. For a **single** API container this is fine. Do **not** scale the API to multiple replicas without replacing `InMemoryRefreshTokenStore` with Redis or similar.
### Logs
API logs: `docker compose logs api` and files under the container at `logs/mealplan-*.log` (ephemeral unless you mount a volume).
### Backups
Back up your **PostgreSQL** database on the DB host. This app does not manage backups.
### Swagger
Swagger is **disabled** in `Production` (`ASPNETCORE_ENVIRONMENT=Production` in compose).
---
## Troubleshooting
| Problem | What to check |
|---------|----------------|
| 502 / empty `/api` | `docker compose logs api`; DB connection string; is `api` healthy? |
| CORS errors in browser | `CORS_ALLOWED_ORIGIN` must exactly match `https://your-domain` |
| DB connection refused | Firewall, `pg_hba.conf`, wrong host/port in `DB_CONNECTION_STRING` |
| Certificate errors | Paths in `docker-compose.tls.yml`; cert files readable inside container |
| Register/login 404 on API | Rebuild images: `docker compose ... up -d --build` |
---
## Minimal command reference
```bash
# Start
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
# Stop
docker compose -f docker-compose.yml -f docker-compose.prod.yml down
# Logs
docker compose logs -f api frontend
# Restart one service
docker compose restart api
```