If you're using Sveltekit with Docker — or with Caprover, for that matter — chances are you need a Dockerfile. Below you can find two versions: one for adapter-node, and the other for adapter-static.

If you're using adapter-node

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/build build/
COPY --from=builder /app/node_modules node_modules/
COPY package.json ./
EXPOSE 3000
ENV NODE_ENV=production
CMD [ "node", "build" ]

If you're using adapter-static

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production

FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

with adapter-static you need to have the nginx conf

server {
  listen 80;
  server_name _;

  root /usr/share/nginx/html;
  index index.html;

  # Serve the specific .html files for routes like /about
  location / {
    try_files $uri $uri.html $uri/ =404;
  }

  # Static file handling with caching headers
  location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|html)$ {
    expires 6M;
    access_log off;
    add_header Cache-Control "public";
  }

  # Disable caching for index.html to prevent outdated builds
  location = /index.html {
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    expires -1;
  }

  # Optional: health check endpoint
  location /health {
    return 200 "healthy\n";
    add_header Content-Type text/plain;
  }

  # Custom error handling
  error_page 404 /404.html;
}

Also in svelte.config.js, check the fallback

import adapter from '@sveltejs/adapter-static';
import sveltePreprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	kit: {
		adapter: adapter({
      fallback: '404.html' // this filename can be whatever you want
    })
  }
};

export default config;