Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Deploy to Test

on:
workflow_dispatch: # For manual triggers via the GitHub Actions UI
inputs:
image-tag:
type: string
description: 'Image tag to deploy (default: pr-<number> if PR exists, latest for default branch)'

jobs:
deploy:
uses: ls1intum/.github/.github/workflows/deploy-docker-compose.yml@main
with:
environment: Test
docker-compose-file: './docker/test/docker-compose.yml'
main-image-name: ls1intum/memo/memo-app
image-tag: ${{ inputs.image-tag }}
env-file-name: ./docker/test/.env
remove-volumes: false
secrets: inherit
16 changes: 11 additions & 5 deletions DEPLOYMENT_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
## ✅ **GitHub Actions Workflows**

- [x] `build-and-push.yml` - Builds and pushes Docker images
- [x] `deploy-test.yml` - Deploys to test environment (memo-test1.aet.cit.tum.de)
- [x] `deploy-staging.yml` - Deploys to staging environment
- [x] `deploy-production.yml` - Deploys to production environment
- [x] No development deployment workflow (correct!)

## ✅ **Docker Compose Files**

- [x] `docker/development/docker-compose.yml` - Local development (uses build:)
- [x] `docker/test/docker-compose.yml` - Test environment (memo-test1.aet.cit.tum.de)
- [x] `docker/staging/docker-compose.yml` - Uses registry image with ${IMAGE_TAG}
- [x] `docker/production/docker-compose.yml` - Uses registry image with ${IMAGE_TAG}

## ✅ **Environment Files**

- [x] `docker/development/.env` - Local development config (localhost URLs)
- [x] `docker/test/.env.example` - Test config (memo-test1.aet.cit.tum.de)

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checklist mentions a docker/test/.env.example file, but this file was not included in the PR. This file should be created to provide a template for the test environment configuration, similar to how other environments have example or actual .env files.

Suggested change
- [x] `docker/test/.env.example` - Test config (memo-test1.aet.cit.tum.de)
- [ ] Test environment `.env` (or `.env.example`) under `docker/test/` to be created for memo-test1.aet.cit.tum.de

Copilot uses AI. Check for mistakes.
- [x] `docker/staging/.env` - Staging config (staging.memo.aet.cit.tum.de)
- [x] `docker/production/.env` - Production config (memo.aet.cit.tum.de)
- [x] All env files have IMAGE_TAG=latest
Expand All @@ -29,6 +32,7 @@
## ✅ **Configuration Files**

- [x] `.dockerignore` - Excludes unnecessary files from build context
- [x] `docker/test/nginx.conf` - Nginx config for test (HTTP + HTTPS)
- [x] `docker/staging/nginx.conf` - Nginx config for staging
- [x] `docker/production/nginx.conf` - Nginx config for production with security

Expand All @@ -38,13 +42,13 @@

1. **Create Environments**:
- Go to `Settings > Environments`
- Create: `Staging`, `Production`
- Create: `Test`, `Staging`, `Production`

2. **For each environment, configure**: **Secrets:**
- `VM_SSH_PRIVATE_KEY` - SSH private key for deployment user

**Variables:**
- `VM_HOST` - Server hostname (e.g., `staging.memo.aet.cit.tum.de`, `memo.aet.cit.tum.de`)
- `VM_HOST` - Server hostname (e.g., `memo-test1.aet.cit.tum.de`, `staging.memo.aet.cit.tum.de`, `memo.aet.cit.tum.de`)
- `VM_USERNAME` - `github_deployment`

### VM Setup (for each environment):
Expand All @@ -57,13 +61,15 @@
## 🚀 **Deployment Process**

1. **Code Push** → Automatic image build (`ghcr.io/ls1intum/memo/memo-app:latest` or `pr-<number>`)
2. **Staging Deploy** → Manual trigger via GitHub Actions UI
3. **Production Deploy** → Manual trigger via GitHub Actions UI
2. **Test Deploy** → Manual trigger via GitHub Actions UI
3. **Staging Deploy** → Manual trigger via GitHub Actions UI
4. **Production Deploy** → Manual trigger via GitHub Actions UI

## 🎯 **Environment Usage**

- **Development**: `./docker-manage.sh up development` (local only)
- **Staging**: GitHub Actions deployment (testing)
- **Test**: GitHub Actions deployment (LRZ VM testing at memo-test1.aet.cit.tum.de)
- **Staging**: GitHub Actions deployment (pre-production testing)
- **Production**: GitHub Actions deployment (live)

## ✅ **All Issues Fixed**
Expand Down
58 changes: 58 additions & 0 deletions docker/test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
version: '3.8'

name: memo-test

services:
nginx:
container_name: memo-test-nginx
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
- /var/www/certbot:/var/www/certbot:ro
depends_on:
- app
networks:
- memo-test-network
restart: unless-stopped

app:
container_name: memo-test-app
image: 'ghcr.io/ls1intum/memo/memo-app:${IMAGE_TAG:-latest}'
environment:
- NODE_ENV=production
- APP_ENV=test
- DATABASE_URL=${DATABASE_URL:-postgresql://memo_user:memo_password@db:5432/memo_test}
- NEXT_PUBLIC_APP_ENV=${NEXT_PUBLIC_APP_ENV:-test}
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-https://memo-test1.aet.cit.tum.de/api}
expose:
- '3000'
depends_on:
- db
networks:
- memo-test-network
restart: unless-stopped

db:
container_name: memo-test-db
image: postgres:16-alpine
environment:
- POSTGRES_DB=memo_test
- POSTGRES_USER=memo_user
- POSTGRES_PASSWORD=memo_password
Comment on lines +28 to +45

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded default database password "memo_password" is used in the docker-compose file. While the documentation mentions changing this, it would be safer to not provide any default password and require it to be set in the .env file. This reduces the risk of accidentally deploying with default credentials.

Copilot uses AI. Check for mistakes.
volumes:
- postgres_test_data:/var/lib/postgresql/data
- ../../scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
networks:
- memo-test-network
restart: unless-stopped

volumes:
postgres_test_data:

networks:
memo-test-network:
driver: bridge
88 changes: 88 additions & 0 deletions docker/test/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
events {
worker_connections 1024;
}

http {
upstream app {
server app:3000;
}

# HTTP server - redirect to HTTPS
server {
listen 80;
server_name memo-test1.aet.cit.tum.de;

# Allow Let's Encrypt ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

# Redirect all other HTTP traffic to HTTPS
location / {
return 301 https://$host$request_uri;
}
}

# HTTPS server
server {
listen 443 ssl http2;
server_name memo-test1.aet.cit.tum.de;

# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/memo-test1.aet.cit.tum.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/memo-test1.aet.cit.tum.de/privkey.pem;

# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Content-Security-Policy header includes 'unsafe-inline' which significantly weakens XSS protection. Consider removing 'unsafe-inline' or using nonces/hashes for inline scripts and styles to maintain better security posture.

Suggested change
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' http: https:; style-src 'self' http: https:; img-src 'self' data: blob: http: https:; font-src 'self' data: http: https:; connect-src 'self' http: https:; object-src 'none'" always;

Copilot uses AI. Check for mistakes.

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript;

# API routes
location /api/ {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}

# All other routes (Next.js app)
location / {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}

# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
Loading
Loading