Update to production Let's Encrypt certificates

This commit is contained in:
Eric Gullickson
2025-12-20 11:14:44 -06:00
parent a17944d79f
commit 9eb025a21f
7 changed files with 42 additions and 144 deletions

View File

@@ -25,7 +25,7 @@ RUN npm run build
FROM node:20-alpine AS production FROM node:20-alpine AS production
# Install runtime dependencies only # Install runtime dependencies only
RUN apk add --no-cache dumb-init RUN apk add --no-cache dumb-init curl
# Set working directory # Set working directory
WORKDIR /app WORKDIR /app

View File

@@ -29,10 +29,13 @@ certificatesResolvers:
acme: acme:
email: admin@motovaultpro.com email: admin@motovaultpro.com
storage: /data/acme.json storage: /data/acme.json
httpChallenge: dnsChallenge:
entryPoint: web provider: cloudflare
# Use staging for development delayBeforeCheck: 10
caServer: https://acme-staging-v02.api.letsencrypt.org/directory resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
# Production Let's Encrypt (no caServer = production by default)
# TLS configuration for local development # TLS configuration for local development
tls: tls:

View File

@@ -6,6 +6,8 @@ services:
restart: unless-stopped restart: unless-stopped
command: command:
- --configFile=/etc/traefik/traefik.yml - --configFile=/etc/traefik/traefik.yml
environment:
CF_DNS_API_TOKEN_FILE: /run/secrets/cloudflare-dns-token
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
@@ -16,6 +18,7 @@ services:
- ./config/traefik/middleware.yml:/etc/traefik/middleware.yml:ro - ./config/traefik/middleware.yml:/etc/traefik/middleware.yml:ro
- ./certs:/certs:ro - ./certs:/certs:ro
- traefik_data:/data - traefik_data:/data
- ./secrets/app/cloudflare-dns-token.txt:/run/secrets/cloudflare-dns-token:ro
networks: networks:
frontend: frontend:
ipv4_address: 10.96.1.50 ipv4_address: 10.96.1.50
@@ -73,6 +76,7 @@ services:
- "traefik.http.routers.mvp-frontend.rule=(Host(`motovaultpro.com`) || Host(`www.motovaultpro.com`)) && !PathPrefix(`/api`)" - "traefik.http.routers.mvp-frontend.rule=(Host(`motovaultpro.com`) || Host(`www.motovaultpro.com`)) && !PathPrefix(`/api`)"
- "traefik.http.routers.mvp-frontend.entrypoints=websecure" - "traefik.http.routers.mvp-frontend.entrypoints=websecure"
- "traefik.http.routers.mvp-frontend.tls=true" - "traefik.http.routers.mvp-frontend.tls=true"
- "traefik.http.routers.mvp-frontend.tls.certresolver=letsencrypt"
- "traefik.http.routers.mvp-frontend.priority=10" - "traefik.http.routers.mvp-frontend.priority=10"
- "traefik.http.services.mvp-frontend.loadbalancer.server.port=3000" - "traefik.http.services.mvp-frontend.loadbalancer.server.port=3000"
- "traefik.http.services.mvp-frontend.loadbalancer.healthcheck.path=/" - "traefik.http.services.mvp-frontend.loadbalancer.healthcheck.path=/"
@@ -128,11 +132,13 @@ services:
- "traefik.http.routers.mvp-backend.rule=(Host(`motovaultpro.com`) || Host(`www.motovaultpro.com`)) && PathPrefix(`/api`)" - "traefik.http.routers.mvp-backend.rule=(Host(`motovaultpro.com`) || Host(`www.motovaultpro.com`)) && PathPrefix(`/api`)"
- "traefik.http.routers.mvp-backend.entrypoints=websecure" - "traefik.http.routers.mvp-backend.entrypoints=websecure"
- "traefik.http.routers.mvp-backend.tls=true" - "traefik.http.routers.mvp-backend.tls=true"
- "traefik.http.routers.mvp-backend.tls.certresolver=letsencrypt"
- "traefik.http.routers.mvp-backend.priority=20" - "traefik.http.routers.mvp-backend.priority=20"
# Health check router (bypass auth) # Health check router (bypass auth)
- "traefik.http.routers.mvp-backend-health.rule=(Host(`motovaultpro.com`) || Host(`www.motovaultpro.com`)) && Path(`/api/health`)" - "traefik.http.routers.mvp-backend-health.rule=(Host(`motovaultpro.com`) || Host(`www.motovaultpro.com`)) && Path(`/api/health`)"
- "traefik.http.routers.mvp-backend-health.entrypoints=websecure" - "traefik.http.routers.mvp-backend-health.entrypoints=websecure"
- "traefik.http.routers.mvp-backend-health.tls=true" - "traefik.http.routers.mvp-backend-health.tls=true"
- "traefik.http.routers.mvp-backend-health.tls.certresolver=letsencrypt"
- "traefik.http.routers.mvp-backend-health.priority=30" - "traefik.http.routers.mvp-backend-health.priority=30"
# Service configuration # Service configuration
- "traefik.http.services.mvp-backend.loadbalancer.server.port=3001" - "traefik.http.services.mvp-backend.loadbalancer.server.port=3001"

View File

@@ -86,6 +86,7 @@ These variables use GitLab's **File** type, which writes the value to a temporar
| `AUTH0_CLIENT_SECRET` | File | Yes | Yes | Auth0 client secret for backend | | `AUTH0_CLIENT_SECRET` | File | Yes | Yes | Auth0 client secret for backend |
| `GOOGLE_MAPS_API_KEY` | File | Yes | Yes | Google Maps API key | | `GOOGLE_MAPS_API_KEY` | File | Yes | Yes | Google Maps API key |
| `GOOGLE_MAPS_MAP_ID` | File | Yes | No | Google Maps Map ID | | `GOOGLE_MAPS_MAP_ID` | File | Yes | No | Google Maps Map ID |
| `CF_DNS_API_TOKEN` | File | Yes | Yes | Cloudflare API token for Let's Encrypt DNS challenge |
### Configuration Variables ### Configuration Variables
@@ -97,6 +98,20 @@ These variables use GitLab's **File** type, which writes the value to a temporar
Note: `DEPLOY_PATH` is automatically set in `.gitlab-ci.yml` using `GIT_CLONE_PATH` for a stable path. Note: `DEPLOY_PATH` is automatically set in `.gitlab-ci.yml` using `GIT_CLONE_PATH` for a stable path.
### Creating Cloudflare API Token
The `CF_DNS_API_TOKEN` is required for automatic SSL certificate generation via Let's Encrypt DNS-01 challenge.
1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com/profile/api-tokens)
2. Click **Create Token**
3. Use template: **Edit zone DNS**
4. Configure permissions:
- **Permissions**: Zone > DNS > Edit
- **Zone Resources**: Include > Specific zone > `motovaultpro.com`
5. Click **Continue to summary** then **Create Token**
6. Copy the token value immediately (it won't be shown again)
7. Add as `CF_DNS_API_TOKEN` File variable in GitLab
### Setting Up a File Type Variable ### Setting Up a File Type Variable
1. Go to **Settings > CI/CD > Variables** 1. Go to **Settings > CI/CD > Variables**
@@ -130,6 +145,7 @@ secrets/app/
auth0-client-secret.txt -> /run/secrets/auth0-client-secret auth0-client-secret.txt -> /run/secrets/auth0-client-secret
google-maps-api-key.txt -> /run/secrets/google-maps-api-key google-maps-api-key.txt -> /run/secrets/google-maps-api-key
google-maps-map-id.txt -> /run/secrets/google-maps-map-id google-maps-map-id.txt -> /run/secrets/google-maps-map-id
cloudflare-dns-token.txt -> /run/secrets/cloudflare-dns-token
``` ```
### Security Benefits ### Security Benefits

View File

@@ -23,8 +23,8 @@ Your task is to create a plan that can be dispatched to a seprate set of AI agen
You are a senior DevOps SRE in charge of MotoVaultPro. A automotive fleet management application. You are a senior DevOps SRE in charge of MotoVaultPro. A automotive fleet management application.
*** ACTION *** *** ACTION ***
- There are errors in deployment then also console errors - The production deployment from GitLab CI is not installing the Let's Encrypt certificates
- It appears when trying to run the npm migrations there is an error. - You should start looking at if the cloudflare API key is being passed into the pipeline.
- Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change. - Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change.
*** CONTEXT *** *** CONTEXT ***
@@ -32,135 +32,3 @@ You are a senior DevOps SRE in charge of MotoVaultPro. A automotive fleet manage
*** CHANGES TO IMPLEMENT *** *** CHANGES TO IMPLEMENT ***
- Plan and recommend the best solution for this change - Plan and recommend the best solution for this change
Here are the relevant logs.
egullickson@mvp-runner-1:~/motovaultpro$ docker compose run --rm mvp-backend npm run migrate
[+] 3/3t 3/31
✔ Network motovaultpro_backend Created 0.1s
✔ Container mvp-postgres Running 0.0s
✔ Container mvp-redis Running 0.0s
Image motovaultpro-mvp-backend Building
[+] Building 196.2s (26/26) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 622B
=> => unpacking to docker.io/library/motovaultpro-mvp-backend:latest 9.5s
=> resolving provenance for metadata file 0.0s
Image motovaultpro-mvp-backend Built
Container motovaultpro-mvp-backend-run-9a43629523b7 Creating
Container motovaultpro-mvp-backend-run-9a43629523b7 Created
npm error Missing script: "migrate"
npm error
npm error To see a list of scripts, run:
npm error npm run
npm error A complete log of this run can be found in: /home/nodejs/.npm/_logs/2025-12-20T16_15_34_540Z-debug-0.log
index-BZX6X6vG.js:21 [Auth Gate] Module loaded, authInitialized: false
index-BZX6X6vG.js:21 [Navigation] State rehydrated successfully
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: true isAuthenticated: false isAuthGateReady: false
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:21 [useIsAuthInitialized] Starting poll for auth init
index-BZX6X6vG.js:27 DataSync: Skipping prefetch - user not authenticated
index-BZX6X6vG.js:27 Window width: 1728 User agent mobile: false Mobile mode: false
index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: true, Authenticated: false, User: null
index-BZX6X6vG.js:21 [Auth Debug] State check: Object
index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: false (was: false )
index-BZX6X6vG.js:21 [IndexedDB] Loaded 0 items into cache
index-BZX6X6vG.js:21 [IndexedDB] Storage initialized successfully
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #1: initialized=false
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #2: initialized=false
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #3: initialized=false
index-BZX6X6vG.js:21 [Auth0Provider] Redirect callback triggered Object
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state
index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: false, Authenticated: true, User: present
index-BZX6X6vG.js:21 [Auth Debug] State check: Object
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state
index-BZX6X6vG.js:21 [Auth] IndexedDB storage is ready
index-BZX6X6vG.js:21 [Mobile Auth] Initializing token cache (mobile: false, delay: 100ms)
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #4: initialized=false
index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object
index-BZX6X6vG.js:21 [Mobile Auth] Token pre-warming successful
index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: true (was: false )
index-BZX6X6vG.js:21 [DEBUG Auth Gate] Authentication fully initialized
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #5: initialized=true
index-BZX6X6vG.js:21 [useIsAuthInitialized] Auth initialized via poll!
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Query function called - fetching vehicles
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:21 Uncaught Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at yi (index-BZX6X6vG.js:21:32113)
at hi (index-BZX6X6vG.js:21:31638)
at Yc (index-BZX6X6vG.js:21:64112)
at Hc (index-BZX6X6vG.js:21:63724)
at index-BZX6X6vG.js:27:314956
at Zu (index-BZX6X6vG.js:21:97033)
at Ad (index-BZX6X6vG.js:21:113396)
at Fd (index-BZX6X6vG.js:21:113280)
at Ad (index-BZX6X6vG.js:21:113441)
at Fd (index-BZX6X6vG.js:21:113280)

View File

@@ -31,6 +31,9 @@ RUN npm run build
# Stage 4: Production stage with nginx # Stage 4: Production stage with nginx
FROM nginx:alpine AS production FROM nginx:alpine AS production
# Add curl for healthchecks
RUN apk add --no-cache curl
# Create non-root user compatible with nginx # Create non-root user compatible with nginx
RUN addgroup -g 1001 -S nodejs && \ RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 -G nginx adduser -S nodejs -u 1001 -G nginx

View File

@@ -11,6 +11,7 @@
# - AUTH0_CLIENT_SECRET # - AUTH0_CLIENT_SECRET
# - GOOGLE_MAPS_API_KEY # - GOOGLE_MAPS_API_KEY
# - GOOGLE_MAPS_MAP_ID # - GOOGLE_MAPS_MAP_ID
# - CF_DNS_API_TOKEN (Cloudflare DNS API token for Let's Encrypt certificates)
# #
# Required GitLab CI/CD Variables (Variable type): # Required GitLab CI/CD Variables (Variable type):
# - DEPLOY_PATH # - DEPLOY_PATH
@@ -70,6 +71,7 @@ inject_secret "POSTGRES_PASSWORD" "postgres-password.txt" || FAILED=1
inject_secret "AUTH0_CLIENT_SECRET" "auth0-client-secret.txt" || FAILED=1 inject_secret "AUTH0_CLIENT_SECRET" "auth0-client-secret.txt" || FAILED=1
inject_secret "GOOGLE_MAPS_API_KEY" "google-maps-api-key.txt" || FAILED=1 inject_secret "GOOGLE_MAPS_API_KEY" "google-maps-api-key.txt" || FAILED=1
inject_secret "GOOGLE_MAPS_MAP_ID" "google-maps-map-id.txt" || FAILED=1 inject_secret "GOOGLE_MAPS_MAP_ID" "google-maps-map-id.txt" || FAILED=1
inject_secret "CF_DNS_API_TOKEN" "cloudflare-dns-token.txt" || FAILED=1
if [ $FAILED -eq 1 ]; then if [ $FAILED -eq 1 ]; then
echo "" echo ""