feat: add Docker Compose dev environment with nginx reverse proxy
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo:7
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
healthcheck:
|
||||||
|
test: echo 'db.runCommand("ping").ok' | mongosh --quiet
|
||||||
|
interval: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 20s
|
||||||
|
backend:
|
||||||
|
container_name: paragonplayground-backend
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "5000:8080"
|
||||||
|
environment:
|
||||||
|
- MongoDb__ConnectionString=mongodb://mongodb:27017
|
||||||
|
- MongoDb__DatabaseName=paragon_playground
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_URLS=http://+:8080
|
||||||
|
depends_on:
|
||||||
|
mongodb:
|
||||||
|
condition: service_healthy
|
||||||
|
frontend:
|
||||||
|
container_name: paragonplayground-frontend
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- ./frontend/src:/app/src
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
- ./certs:/etc/nginx/certs
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
- frontend
|
||||||
|
volumes:
|
||||||
|
mongo_data:
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name ~^(?<org_slug>[a-z0-9-]+)\.paragonplayground\.localhost$;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/_wildcard.paragonplayground.localhost.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/_wildcard.paragonplayground.localhost-key.pem;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend:8080;
|
||||||
|
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 X-Organization-Slug $org_slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://frontend:3000;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ~^(?<org_slug>[a-z0-9-]+)\.paragonplayground\.localhost$;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name paragonplayground.localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 400 "Missing organization subdomain. Use https://<org-slug>.paragonplayground.localhost";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Builds the .NET solution.
|
||||||
|
.PARAMETER Configuration
|
||||||
|
Build configuration (Debug | Release). Defaults to Debug.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[ValidateSet('Debug', 'Release')]
|
||||||
|
[string]$Configuration = 'Debug'
|
||||||
|
)
|
||||||
|
|
||||||
|
$repoRoot = Join-Path $PSScriptRoot '..'
|
||||||
|
$sln = Join-Path $repoRoot 'backend\ParagonPlayground.slnx'
|
||||||
|
|
||||||
|
if (-not (Test-Path $sln)) {
|
||||||
|
Write-Host "Solution not found at $sln" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Building $Configuration..." -ForegroundColor Cyan
|
||||||
|
dotnet build $sln --configuration $Configuration
|
||||||
|
exit $LASTEXITCODE
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Cleans .NET build artifacts (bin/obj) for all projects in the solution.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$repoRoot = Join-Path $PSScriptRoot '..'
|
||||||
|
$sln = Join-Path $repoRoot 'backend\ParagonPlayground.slnx'
|
||||||
|
|
||||||
|
if (-not (Test-Path $sln)) {
|
||||||
|
Write-Host "Solution not found at $sln" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Running dotnet clean..." -ForegroundColor Cyan
|
||||||
|
dotnet clean $sln --verbosity quiet
|
||||||
|
|
||||||
|
Write-Host "Removing bin/obj directories..." -ForegroundColor Cyan
|
||||||
|
$dirs = Get-ChildItem -Path (Join-Path $repoRoot 'backend') -Include bin,obj -Recurse -Directory
|
||||||
|
$dirs | Remove-Item -Recurse -Force
|
||||||
|
|
||||||
|
Write-Host "Cleaned $($dirs.Count) directories." -ForegroundColor Green
|
||||||
|
exit $LASTEXITCODE
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Stops the development environment and removes containers/networks.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$composeFile = Join-Path $PSScriptRoot '..\docker-compose.dev.yml'
|
||||||
|
|
||||||
|
if (-not (Test-Path $composeFile)) {
|
||||||
|
Write-Host "Compose file not found at $composeFile" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Stopping development environment..." -ForegroundColor Cyan
|
||||||
|
docker compose -f $composeFile down
|
||||||
|
exit $LASTEXITCODE
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Starts the development environment via Docker Compose.
|
||||||
|
.PARAMETER Detached
|
||||||
|
Run containers in the background. Defaults to true.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[switch]$Detached = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
$composeFile = Join-Path $PSScriptRoot '..\docker-compose.dev.yml'
|
||||||
|
|
||||||
|
if (-not (Test-Path $composeFile)) {
|
||||||
|
Write-Host "Compose file not found at $composeFile" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Starting development environment..." -ForegroundColor Cyan
|
||||||
|
$detachArg = if ($Detached) { '-d' } else { '' }
|
||||||
|
docker compose -f $composeFile up --build $detachArg
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0 -and $Detached) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "All services started. Run 'scripts/dev-down.ps1' to stop." -ForegroundColor Green
|
||||||
|
Write-Host "Open https://acme.paragonplayground.localhost in your browser." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
exit $LASTEXITCODE
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Restores .NET packages and installs frontend npm dependencies.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$projectRoot = Join-Path $PSScriptRoot '..'
|
||||||
|
$backendDir = Join-Path $projectRoot 'backend'
|
||||||
|
$frontendDir = Join-Path $projectRoot 'frontend'
|
||||||
|
$sln = Join-Path $backendDir 'ParagonPlayground.slnx'
|
||||||
|
|
||||||
|
Write-Host "Restoring .NET packages..." -ForegroundColor Cyan
|
||||||
|
if (Test-Path $sln) {
|
||||||
|
dotnet restore $sln
|
||||||
|
} else {
|
||||||
|
dotnet restore $backendDir
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nInstalling frontend npm dependencies..." -ForegroundColor Cyan
|
||||||
|
if (Test-Path $frontendDir) {
|
||||||
|
Push-Location $frontendDir
|
||||||
|
npm install
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nAll dependencies installed." -ForegroundColor Green
|
||||||
|
exit $LASTEXITCODE
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Generates local TLS certificates for *.paragonplayground.localhost using mkcert.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Uses mkcert (https://github.com/FiloSottile/mkcert) to create a local
|
||||||
|
Certificate Authority and sign a wildcard certificate for local development.
|
||||||
|
The CA is automatically trusted by the OS and browsers (no security warnings).
|
||||||
|
|
||||||
|
If mkcert is not installed, the script will offer to install it via winget.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Run this script from an elevated (Admin) PowerShell prompt so mkcert can
|
||||||
|
install the CA into the JDK system trust store (cacerts). Without elevation
|
||||||
|
the CA is still trusted by the OS and browsers, but Java tools may reject
|
||||||
|
the certificate.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$certsDir = Join-Path $PSScriptRoot "..\certs"
|
||||||
|
|
||||||
|
if (-not (Test-Path $certsDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $certsDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if mkcert is available
|
||||||
|
$mkcert = Get-Command mkcert -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if (-not $mkcert) {
|
||||||
|
Write-Host "mkcert not found." -ForegroundColor Yellow
|
||||||
|
$install = Read-Host "Install mkcert via winget? (y/n)"
|
||||||
|
|
||||||
|
if ($install -eq "y") {
|
||||||
|
winget install FiloSottile.mkcert
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Installation failed. Install manually: https://github.com/FiloSottile/mkcert" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";$env:Path"
|
||||||
|
} else {
|
||||||
|
Write-Host "Install mkcert manually from https://github.com/FiloSottile/mkcert and re-run." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Installing local CA (may prompt for admin)..." -ForegroundColor Cyan & mkcert -install
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Failed to install CA." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Generating certificate for *.paragonplayground.localhost..." -ForegroundColor Cyan & mkcert -cert-file "$certsDir/_wildcard.paragonplayground.localhost.pem" `
|
||||||
|
-key-file "$certsDir/_wildcard.paragonplayground.localhost-key.pem" `
|
||||||
|
"*.paragonplayground.localhost"
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "Certificates generated in $certsDir" -ForegroundColor Green
|
||||||
|
Write-Host " cert: $certsDir\_wildcard.paragonplayground.localhost.pem" -ForegroundColor Green
|
||||||
|
Write-Host " key: $certsDir\_wildcard.paragonplayground.localhost-key.pem" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Certificate generation failed." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Runs .NET tests for the solution.
|
||||||
|
.PARAMETER Configuration
|
||||||
|
Build configuration (Debug | Release). Defaults to Debug.
|
||||||
|
.PARAMETER NoBuild
|
||||||
|
Skip build step. Defaults to false.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[ValidateSet('Debug', 'Release')]
|
||||||
|
[string]$Configuration = 'Debug',
|
||||||
|
[switch]$NoBuild
|
||||||
|
)
|
||||||
|
|
||||||
|
$repoRoot = Join-Path $PSScriptRoot '..'
|
||||||
|
$sln = Join-Path $repoRoot 'backend\ParagonPlayground.slnx'
|
||||||
|
|
||||||
|
if (-not (Test-Path $sln)) {
|
||||||
|
Write-Host "Solution not found at $sln" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$noBuildArg = if ($NoBuild) { '--no-build' } else { '' }
|
||||||
|
|
||||||
|
Write-Host "Running tests ($Configuration)..." -ForegroundColor Cyan
|
||||||
|
dotnet test $sln --configuration $Configuration $noBuildArg --verbosity normal
|
||||||
|
exit $LASTEXITCODE
|
||||||
Reference in New Issue
Block a user