chore: add Dockerfile and docker-compose with static file serving

Multi-stage Docker build (web → server → production). Fastify serves
React build in production with SPA fallback. Docker Compose with volume
for SQLite persistence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bas van Rossem
2026-02-19 16:30:17 +01:00
parent 510820b77a
commit 9ef1199324
4 changed files with 96 additions and 2 deletions

View File

@@ -1,7 +1,10 @@
import { createServer } from 'node:http';
import { join } from 'node:path';
import { existsSync } from 'node:fs';
import Fastify from 'fastify';
import cors from '@fastify/cors';
import { PORT } from './config.js';
import fastifyStatic from '@fastify/static';
import { PORT, NODE_ENV } from './config.js';
import { runMigrations } from './db/migrate.js';
import { healthRoutes } from './routes/health.js';
import { shipRoutes } from './routes/ships.js';
@@ -27,8 +30,26 @@ const start = async () => {
await app.register(shipRoutes);
await app.register(weaponRoutes);
// In production, serve the React build as static files
const webDistPath = join(process.cwd(), 'web-dist');
if (NODE_ENV === 'production' && existsSync(webDistPath)) {
await app.register(fastifyStatic, {
root: webDistPath,
prefix: '/',
wildcard: false,
});
// SPA fallback: non-API GET requests return index.html
app.setNotFoundHandler((request, reply) => {
if (request.method === 'GET' && !request.url.startsWith('/api')) {
return reply.sendFile('index.html');
}
reply.code(404).send({ error: 'Not found' });
});
}
await app.listen({ port: PORT, host: '0.0.0.0' });
console.log(`Server running on port ${PORT}`);
console.log(`Server running on port ${PORT} (${NODE_ENV})`);
} catch (err) {
app.log.error(err);
process.exit(1);