build(website): Dockerfile + production start script for Coolify

Make the marketing site deployable standalone from apps/website (the repo
has no root package.json, so the build must run in this subdir — set Base
Directory to apps/website in Coolify).

- Add a multi-stage Dockerfile (pnpm build -> node .output/server/index.mjs,
  port 3000, NUXT_PUBLIC_SITE_URL default) + .dockerignore.
- Add a "start" script for the Nixpacks path (Nuxt SSR has no default start).
- Guard the bind-mount-only /shared-packages components dir with existsSync
  so standalone builds don't warn on the missing path (the site uses no
  shared component).
This commit is contained in:
Ronni Baslund
2026-06-06 21:33:41 +02:00
parent 554cb99f2c
commit c9e22ec117
4 changed files with 40 additions and 1 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules
.nuxt
.output
.git
dist
*.log
+25
View File
@@ -0,0 +1,25 @@
# Production image for the dezky marketing site (Nuxt 4 SSR).
# Build context = this directory (apps/website). In Coolify set the build pack
# to "Dockerfile" and Base Directory to apps/website.
# syntax=docker/dockerfile:1
FROM node:22-alpine AS build
WORKDIR /app
RUN corepack enable
# Install deps first for layer caching (pnpm version comes from packageManager).
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
# Absolute canonical / OG / sitemap URLs. Override in Coolify if the domain
# differs (e.g. a staging URL).
ENV NUXT_PUBLIC_SITE_URL=https://dezky.eu
COPY --from=build /app/.output ./.output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
+8 -1
View File
@@ -6,6 +6,8 @@
// the Docker app stack. Locally it runs behind Traefik at dezky.local / // the Docker app stack. Locally it runs behind Traefik at dezky.local /
// www.dezky.local with the same mkcert TLS as the rest of the platform. // www.dezky.local with the same mkcert TLS as the rest of the platform.
import { existsSync } from 'node:fs'
const siteUrl = process.env.NUXT_PUBLIC_SITE_URL const siteUrl = process.env.NUXT_PUBLIC_SITE_URL
|| (process.env.NODE_ENV === 'production' ? 'https://dezky.eu' : 'http://localhost:3000') || (process.env.NODE_ENV === 'production' ? 'https://dezky.eu' : 'http://localhost:3000')
@@ -25,7 +27,12 @@ export default defineNuxtConfig({
// CountrySelect.vue is just <CountrySelect>. Mirrors portal/operator. // CountrySelect.vue is just <CountrySelect>. Mirrors portal/operator.
components: [ components: [
'~/components', '~/components',
{ path: '/shared-packages/ui/components', pathPrefix: false }, // The shared @dezky/ui dir is bind-mounted in the Docker dev stack; include
// it only when present so standalone builds (e.g. Coolify) don't warn on
// the missing absolute path. The marketing site uses no shared component.
...(existsSync('/shared-packages/ui/components')
? [{ path: '/shared-packages/ui/components', pathPrefix: false }]
: []),
], ],
app: { app: {
+1
View File
@@ -6,6 +6,7 @@
"scripts": { "scripts": {
"dev": "TMPDIR=/tmp nuxt dev --host 0.0.0.0 --port 3000", "dev": "TMPDIR=/tmp nuxt dev --host 0.0.0.0 --port 3000",
"build": "nuxt build", "build": "nuxt build",
"start": "node .output/server/index.mjs",
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",
"typecheck": "nuxt typecheck", "typecheck": "nuxt typecheck",