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

6.3 KiB
Raw Permalink Blame History

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

# 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):

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):

psql "host=YOUR_DB_HOST port=5432 dbname=YOUR_DB user=YOUR_USER password=YOUR_PASS" -c "SELECT 1"

3. Deploy the application

# 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)

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:

openssl rand -base64 48

Build and start (production overlay hides public API port 5000):

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build

Check status:

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.

Install Certbot:

sudo apt install -y certbot

Stop the frontend container so port 80 is free:

cd ~/dailymeals
docker compose -f docker-compose.yml -f docker-compose.prod.yml stop frontend

Obtain certificate:

sudo certbot certonly --standalone -d meals.example.com --agree-tos -m you@example.com

Copy and edit the TLS compose file:

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:

CORS_ALLOWED_ORIGIN=https://meals.example.com

Start again:

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):

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)

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

# 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