carservice: initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
.next
|
||||
coverage
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.git
|
||||
.vscode
|
||||
.idea
|
||||
.DS_Store
|
||||
prisma/*.db
|
||||
prisma/*.db-journal
|
||||
@@ -0,0 +1,11 @@
|
||||
# Staging environment variables
|
||||
# Copy this file to .env in the deployment directory before running docker compose
|
||||
|
||||
# Prisma SQLite database path (relative to app root inside container)
|
||||
DATABASE_URL=file:./prisma/dev.db
|
||||
|
||||
# Optional: DVLA API key for UK vehicle lookups
|
||||
DVLA_API_KEY=
|
||||
|
||||
# Optional: MotorCheck/Cartell API key for Irish vehicle lookups
|
||||
MOTORCHECK_API_KEY=
|
||||
@@ -0,0 +1,50 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependency files
|
||||
COPY package.json package-lock.json* ./
|
||||
COPY prisma ./prisma
|
||||
|
||||
# Install dependencies (including dev deps for build)
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build Next.js app (standalone output)
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
# --- Production stage ---
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S nextjs -u 1001
|
||||
|
||||
# Copy standalone output from builder
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
||||
|
||||
# Generate Prisma client for production runtime
|
||||
RUN npm install prisma --save-dev && npx prisma generate
|
||||
|
||||
# Ensure DB directory exists and is writable
|
||||
RUN mkdir -p /app/prisma && chown -R nextjs:nodejs /app/prisma
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo "=== Andris Car Service — Staging Deploy ==="
|
||||
echo "Project root: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
# Copy .env if missing
|
||||
if [[ ! -f "$SCRIPT_DIR/.env" && -f "$SCRIPT_DIR/.env.staging" ]]; then
|
||||
echo "Copying .env.staging → .env"
|
||||
cp "$SCRIPT_DIR/.env.staging" "$SCRIPT_DIR/.env"
|
||||
fi
|
||||
|
||||
# Build and start
|
||||
cd "$SCRIPT_DIR"
|
||||
docker compose down --remove-orphans 2>/dev/null || true
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
|
||||
echo ""
|
||||
echo "=== Deploy complete ==="
|
||||
echo "App should be available at:"
|
||||
echo " - Nginx (HTTP): http://localhost"
|
||||
echo " - Next.js (direct): http://localhost:3000"
|
||||
echo ""
|
||||
docker compose ps
|
||||
@@ -0,0 +1,11 @@
|
||||
# Docker Compose override for local development / testing.
|
||||
# Usage: docker compose -f docker-compose.yml -f docker-compose.override.yml up
|
||||
services:
|
||||
app:
|
||||
# Mount local source for live-reload during dev (not for staging!)
|
||||
volumes:
|
||||
- ../src:/app/src:ro
|
||||
- ../prisma:/app/prisma:ro
|
||||
- app-db:/app/prisma
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
@@ -0,0 +1,56 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/Dockerfile
|
||||
container_name: andris-app
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=file:./prisma/dev.db
|
||||
- DVLA_API_KEY=${DVLA_API_KEY:-}
|
||||
- MOTORCHECK_API_KEY=${MOTORCHECK_API_KEY:-}
|
||||
volumes:
|
||||
- app-db:/app/prisma
|
||||
- app-logs:/app/logs
|
||||
networks:
|
||||
- andris-net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: andris-nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- cert-data:/etc/letsencrypt
|
||||
- cert-www:/var/www/certbot
|
||||
depends_on:
|
||||
- app
|
||||
networks:
|
||||
- andris-net
|
||||
|
||||
networks:
|
||||
andris-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
app-db:
|
||||
driver: local
|
||||
app-logs:
|
||||
driver: local
|
||||
cert-data:
|
||||
driver: local
|
||||
cert-www:
|
||||
driver: local
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
# If the SQLite DB file does not exist, initialise schema and seed
|
||||
DB_PATH="/app/prisma/dev.db"
|
||||
if [ ! -f "$DB_PATH" ]; then
|
||||
echo "Database not found — initialising..."
|
||||
npx prisma db push --accept-data-loss
|
||||
node prisma/seed-auth.js
|
||||
echo "Database ready."
|
||||
fi
|
||||
|
||||
# Start the Next.js server
|
||||
exec node server.js
|
||||
@@ -0,0 +1,34 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _; # accept any hostname (update to your domain for production)
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Proxy to Next.js app
|
||||
location / {
|
||||
proxy_pass http://app:3000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
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;
|
||||
|
||||
# WebSocket support (for dev HMR; optional in production)
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
# Static file optimization
|
||||
location /_next/static {
|
||||
proxy_pass http://app:3000;
|
||||
proxy_cache_valid 200 365d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
Reference in New Issue
Block a user