This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Node
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
coverage
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Deployment
|
||||
/deployment
|
||||
|
||||
# Prisma (DB file itself should not be baked into image; mounted via volume)
|
||||
prisma/*.db
|
||||
prisma/*.db-journal
|
||||
|
||||
# Scripts (not needed in image)
|
||||
scripts/
|
||||
@@ -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,54 @@
|
||||
# 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
|
||||
|
||||
# Copy entrypoint and make it executable
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/deployment/entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
@@ -0,0 +1,59 @@
|
||||
# Deployment Guide — Staging
|
||||
|
||||
## Quick start (on the staging server)
|
||||
|
||||
1. SSH to the staging server and clone the repo (or copy the project folder).
|
||||
2. Go into the `deployment/` directory.
|
||||
3. Copy the staging environment file:
|
||||
```bash
|
||||
cp .env.staging .env
|
||||
# edit .env to fill in any API keys you need
|
||||
```
|
||||
4. Run the deploy script:
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
5. The app is available on:
|
||||
- **Nginx (HTTP):** http://localhost
|
||||
- **Next.js (direct):** http://localhost:3000
|
||||
|
||||
## What the script does
|
||||
|
||||
- Builds the Docker image from the project root using the `Dockerfile`.
|
||||
- Starts two containers:
|
||||
- `andris-app` — the Next.js app (port 3000, exposed only on localhost).
|
||||
- `andris-nginx` — reverse proxy on ports 80 / 443.
|
||||
- SQLite database is stored in a Docker volume (`app-db`) so data survives restarts.
|
||||
- On first boot the entrypoint auto-initialises the database (`prisma db push`) and seeds the admin user.
|
||||
|
||||
## Updating the app
|
||||
|
||||
```bash
|
||||
cd deployment
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
This rebuilds the image with your latest source code and recreates the containers.
|
||||
|
||||
## Files overview
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Multi-stage build (Next.js standalone) |
|
||||
| `docker-compose.yml` | Production services: app + nginx |
|
||||
| `docker-compose.override.yml` | Optional override for local dev |
|
||||
| `nginx/nginx.conf` | Nginx main config |
|
||||
| `nginx/default.conf` | Nginx site / reverse proxy config |
|
||||
| `.env.staging` | Staging environment template |
|
||||
| `deploy.sh` | One-command build & deploy script |
|
||||
| `entrypoint.sh` | Container entrypoint (DB init + app start) |
|
||||
|
||||
## Health check
|
||||
|
||||
A `GET /api/health` endpoint returns `{"status":"ok"}`.
|
||||
The Docker `healthcheck` uses it every 30 s.
|
||||
|
||||
## Notes
|
||||
|
||||
- The `.dockerignore` at the project root prevents the dev database, `node_modules`, and `.env` from being baked into the image.
|
||||
- For a real domain, update `server_name` in `nginx/default.conf` and mount your TLS certificates.
|
||||
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,35 @@
|
||||
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: let Nginx serve directly if mounted,
|
||||
# otherwise proxied through Next.js is fine.
|
||||
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