progressively working deploy scenario
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.devcontainer
|
||||
.vscode
|
||||
.claude
|
||||
backend
|
||||
docker-compose*.yml
|
||||
.env*
|
||||
*.md
|
||||
.DS_Store
|
||||
+21
-6
@@ -1,9 +1,24 @@
|
||||
# KMountain Flower Radio Station — Environment Variables
|
||||
# Copy this file to .env and fill in the values. Do NOT commit .env.
|
||||
# Copy this file to .env and adjust values for your environment. Do NOT commit .env.
|
||||
|
||||
# PostgreSQL connection string (backend)
|
||||
# Format: postgresql+asyncpg://user:password@host:port/database
|
||||
KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain
|
||||
# ── Database (db service) ─────────────────────────────────────────────
|
||||
POSTGRES_DB=kmountain
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
|
||||
# CORS allowlist for the backend (JSON array of allowed origins)
|
||||
KMTN_CORS_ORIGINS=["http://localhost:4200"]
|
||||
# ── Backend API (api service) ─────────────────────────────────────────
|
||||
# Database connection string. Use postgresql+asyncpg for PostgreSQL, sqlite+aiosqlite for local dev without a DB.
|
||||
KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/kmountain
|
||||
|
||||
# JSON array of allowed CORS origins.
|
||||
# Dev: include localhost variants. Prod: your actual domain(s).
|
||||
KMTN_CORS_ORIGINS='["http://localhost:4200", "http://localhost"]'
|
||||
|
||||
# Environment name. Set to "production" to disable the admin router.
|
||||
# KMTN_ENV=development
|
||||
|
||||
# ── Frontend (frontend service) ───────────────────────────────────────
|
||||
# URL of the API backend that nginx should proxy /api/ requests to.
|
||||
# Dev (Docker Compose): http://api:8000 (DNS resolves to the api container)
|
||||
# Prod: https://api.yourdomain.com (or whatever your API endpoint is)
|
||||
API_UPSTREAM=http://api:8000
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
# ── Build stage ───────────────────────────────────────────────────────
|
||||
FROM node:20-alpine AS build
|
||||
FROM docker.io/library/node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
@@ -7,8 +7,11 @@ COPY . .
|
||||
RUN npx ng build --configuration production
|
||||
|
||||
# ── Serve stage ────────────────────────────────────────────────────────
|
||||
FROM docker.io/library/nginx:1.30-perl
|
||||
FROM docker.io/library/nginx:alpine
|
||||
COPY --from=build /app/dist/radio-station/browser /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY nginx.conf.template /etc/nginx/conf.d/nginx.conf.template
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
ENV API_UPSTREAM=http://api:8000
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
.git
|
||||
.vscode
|
||||
.claude
|
||||
*.egg-info
|
||||
.pytest_cache
|
||||
.mypy_cache
|
||||
.env*
|
||||
docker-compose*.yml
|
||||
*.md
|
||||
.DS_Store
|
||||
+3
-2
@@ -2,11 +2,12 @@ FROM docker.io/library/python:3.12-slim AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install --no-cache-dir gunicorn uvicorn psycopg2-binary
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
|
||||
EXPOSE 8000
|
||||
CMD ["gunicorn", "app.main:app", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "--workers", "2", "--timeout", "120"]
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# Dev-sidecar compose: app container only (no database service).
|
||||
# Used by .devcontainer/devcontainer.json via dockerComposeFile.
|
||||
# Development uses SQLite — no Docker-in-Docker database needed.
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
restart: "no"
|
||||
ports:
|
||||
- "8000:8000" # FastAPI
|
||||
- "4200:4200" # Angular dev server
|
||||
environment:
|
||||
KMTN_DATABASE_URL: sqlite+aiosqlite:///./backend/kmountain.db
|
||||
KMTN_CORS_ORIGINS: '["http://localhost:4200"]'
|
||||
volumes:
|
||||
- ..:/workspaces/web_app:cached
|
||||
- node_modules:/workspaces/web_app/node_modules
|
||||
- backend_venv:/app/.venv
|
||||
command: >
|
||||
sh -c "
|
||||
pip install -r backend/requirements.txt &&
|
||||
cd backend &&
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload &
|
||||
cd /workspaces/web_app &&
|
||||
npm install &&
|
||||
npx ng serve --host 0.0.0.0 --port 4200
|
||||
"
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
backend_venv:
|
||||
@@ -0,0 +1,19 @@
|
||||
# Production override — use with: docker compose -f docker-compose.yml -f docker-compose.prod.yml up
|
||||
# Assumes: managed PostgreSQL, external reverse proxy / load balancer handling TLS and port 80/443.
|
||||
|
||||
services:
|
||||
# No db service — managed PostgreSQL is assumed.
|
||||
# Set KMTN_DATABASE_URL in your environment or a .env file.
|
||||
|
||||
api:
|
||||
environment:
|
||||
KMTN_ENV: production
|
||||
# Override with your actual production domain(s)
|
||||
KMTN_CORS_ORIGINS: '["https://yourdomain.com"]'
|
||||
# No port mapping — traffic arrives via reverse proxy / load balancer
|
||||
|
||||
frontend:
|
||||
environment:
|
||||
# Point to your API's production URL
|
||||
API_UPSTREAM: https://api.yourdomain.com
|
||||
# No port mapping — traffic arrives via reverse proxy / load balancer
|
||||
+3
-1
@@ -25,7 +25,7 @@ services:
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain
|
||||
KMTN_CORS_ORIGINS: '["http://localhost:4200"]'
|
||||
KMTN_CORS_ORIGINS: '["http://localhost:4200", "http://localhost"]'
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
@@ -37,6 +37,8 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
API_UPSTREAM: http://api:8000
|
||||
ports:
|
||||
- "4200:80"
|
||||
depends_on:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Render the nginx config template with environment variables, then start nginx.
|
||||
set -e
|
||||
|
||||
envsubst '${API_UPSTREAM}' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf
|
||||
|
||||
exec nginx -g 'daemon off;'
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# SPA fallback — Angular router handles its own paths
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API calls to the backend service (only when running inside Docker)
|
||||
location /api/ {
|
||||
# When the env var is set, nginx resolves "api" to the backend container.
|
||||
# In dev (HOST_MODE=dev) we proxy to localhost:8000 instead.
|
||||
if ($host = localhost) {
|
||||
# Only proxy when the port matches our dev setup
|
||||
# See: https://serverfault.com/questions/777768/conditional-proxy-pass-in-nginx
|
||||
}
|
||||
proxy_pass http://api:8000;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# SPA fallback — Angular router handles its own paths
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API calls to the backend service
|
||||
location /api/ {
|
||||
proxy_pass ${API_UPSTREAM};
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,13 @@ export class AdminEventFormComponent implements OnChanges {
|
||||
this.active = event.active;
|
||||
}
|
||||
|
||||
formatError(err: any): string {
|
||||
if (err?.error?.detail) return err.error.detail;
|
||||
if (err?.error?.message) return err.error.message;
|
||||
if (err?.message) return err.message;
|
||||
return 'Failed to save. Please try again.';
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.id = null;
|
||||
this.date = '';
|
||||
|
||||
@@ -60,6 +60,13 @@ export class AdminProgramFormComponent implements OnChanges {
|
||||
}
|
||||
|
||||
/** Reset the form for a new entry. */
|
||||
formatError(err: any): string {
|
||||
if (err?.error?.detail) return err.error.detail;
|
||||
if (err?.error?.message) return err.error.message;
|
||||
if (err?.message) return err.message;
|
||||
return 'Failed to save. Please try again.';
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.id = null;
|
||||
this.day_of_week = 1;
|
||||
|
||||
@@ -46,6 +46,13 @@ export class AdminTierFormComponent implements OnChanges {
|
||||
this.display_order = tier.display_order;
|
||||
}
|
||||
|
||||
formatError(err: any): string {
|
||||
if (err?.error?.detail) return err.error.detail;
|
||||
if (err?.error?.message) return err.error.message;
|
||||
if (err?.message) return err.message;
|
||||
return 'Failed to save. Please try again.';
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.id = null;
|
||||
this.name = '';
|
||||
|
||||
Reference in New Issue
Block a user