carservice: initial commit
Build and publish images / docker (push) Canceled after 0s

This commit is contained in:
Hermes
2026-07-20 20:51:23 +01:00
commit f7a9906add
77 changed files with 9403 additions and 0 deletions
+38
View File
@@ -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/
+11
View File
@@ -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=
+54
View File
@@ -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"]
+59
View File
@@ -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.
+29
View File
@@ -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
+11
View File
@@ -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
+56
View File
@@ -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
+14
View File
@@ -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
+35
View File
@@ -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";
}
}
+33
View File
@@ -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;
}