Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
# syntax=docker/dockerfile:1

# ─────────────────────────────────────────────────────────────────────────────
# SpectraCleanse AI – Backend Dockerfile
# Base: node:18-alpine | Exposes port 3001
# SpectraCleanse AI – Production Dockerfile (builds frontend + backend runtime)
# ─────────────────────────────────────────────────────────────────────────────

FROM node:18-alpine
FROM node:18-bookworm-slim AS builder
WORKDIR /app

# ExifTool requires Perl, which is not included in Alpine by default
RUN apk add --no-cache perl
COPY package.json package-lock.json ./
RUN npm ci

# Create a non-root user to run the process
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY . .
RUN npm run build

FROM node:18-bookworm-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app

Comment on lines +10 to 19
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Consider reusing node_modules from the builder stage to avoid a second full npm install

Dependencies are currently installed twice (npm ci in the builder and npm ci --omit=dev in the runtime). To reduce build time and network usage, you could copy node_modules from the builder into the runtime image and then run npm prune --production (or equivalent) there, preserving the multi-stage setup without a second full install.

Suggested implementation:

# SpectraCleanse AI – Production Dockerfile (builds frontend + backend runtime)
# ─────────────────────────────────────────────────────────────────────────────

FROM node:18-bookworm-slim AS builder
WORKDIR /app

# Install full dependency tree (including devDependencies) for build
COPY package.json package-lock.json ./
RUN npm ci

# Copy source and build
COPY . .
RUN npm run build

To fully implement reusing node_modules from the builder in the runtime image, you should also:

  1. In the runtime stage (the FROM node:18-bookworm-slim AS runtime section), replace the second npm ci --omit=dev with copying node_modules from the builder and pruning dev dependencies, e.g.:

    # Instead of:
    COPY package.json package-lock.json ./
    RUN npm ci --omit=dev \
      && npm cache clean --force
    
    # Use:
    COPY package.json package-lock.json ./
    COPY --from=builder /app/node_modules ./node_modules
    
    RUN npm prune --production \
      && npm cache clean --force
  2. Ensure you copy the built artifacts from the builder into the runtime image (if not already present), for example:

    COPY --from=builder /app/dist ./dist
  3. Keep the existing Perl installation and non-root user setup in the runtime stage as-is, so that exiftool-vendored continues to work and the container still runs as appuser.

Adjust paths like /app/dist or the final CMD to match your existing build output and startup command.

# Install dependencies first so Docker can cache this layer
COPY package*.json ./
RUN npm ci --omit=dev
# exiftool-vendored requires Perl at runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends perl \
&& rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd --system appgroup && useradd --system --gid appgroup appuser

COPY package.json package-lock.json ./
RUN npm ci --omit=dev \
&& npm cache clean --force

# Copy application source
COPY server.js ./
COPY --from=builder /app/dist ./dist

# Runtime uploads directory (ephemeral; processed files are deleted after download)
# Persistent data directory for SQLite – mount a volume here in production
RUN mkdir -p uploads /data && chown -R appuser:appgroup /app uploads /data
# Runtime directories (uploads ephemeral; /data intended for SQLite volume mounts)
RUN mkdir -p /app/uploads /data \
&& chown -R appuser:appgroup /app /data

# Drop to non-root for all subsequent instructions and at runtime
USER appuser

EXPOSE 3001

CMD ["node", "server.js"]
CMD ["npm", "start"]
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,52 @@ The source code is available at [github.com/ChrisAdamsdevelopment/SpectraCleanse
## Contact

Questions, partnerships, or enterprise enquiries: [hello@spectracleanse.com](mailto:hello@spectracleanse.com)

---

## Docker production deployment

This repository includes a multi-stage `Dockerfile` that builds the frontend and packages `dist/` into the final runtime image so `server.js` can serve the SPA in production.

### Build image

```bash
docker build -t spectracleanseai:latest .
```

### Run container

```bash
docker run --rm -p 3001:3001 \
-e NODE_ENV=production \
-e JWT_SECRET=your_jwt_secret \
-e STRIPE_SECRET_KEY=sk_live_xxx \
-e STRIPE_WEBHOOK_SECRET=whsec_xxx \
-e STRIPE_CREATOR_PRICE_ID=price_xxx \
-e STRIPE_STUDIO_PRICE_ID=price_xxx \
-e GEMINI_API_KEY=your_gemini_api_key \
-e FRONTEND_URL=https://your-frontend-domain.example \
-e DB_PATH=/data/spectra.db \
-v spectracleanse_data:/data \
spectracleanseai:latest
```

### Required production environment variables

- `NODE_ENV=production`
- `JWT_SECRET`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Clarify that JWT_SECRET should be a strong, unique secret in production.

Add a brief note to this bullet that JWT_SECRET must be a strong, unique value and not reused across environments to avoid weak production configurations.

Suggested change
- `JWT_SECRET`
- `JWT_SECRET` – a strong, unique secret value; **do not** reuse across environments (e.g., dev/staging/production)

- `STRIPE_SECRET_KEY`
- `STRIPE_WEBHOOK_SECRET`
- `STRIPE_CREATOR_PRICE_ID`
- `STRIPE_STUDIO_PRICE_ID`
- `GEMINI_API_KEY`
- `FRONTEND_URL`
- `DB_PATH`
- `REDIS_URL` (only if your deployment still uses Redis externally)

### Stripe vs local mock checkout

- Local development may use `ENABLE_MOCK_CHECKOUT=true` when Stripe variables are not set.
- Production must use real Stripe configuration; do not rely on mock checkout in production.

Never commit real secrets to source control.
Loading