feat: add CI/CD pipeline, production Dockerfile, and deployment architecture

- Add production Dockerfile with bun build --compile, multi-stage Alpine build
- Add .dockerignore
- Swap bcrypt -> bcryptjs (pure JS, no native addons)
- Add programmatic migrations on startup via drizzle migrator
- Add /v1/version endpoint with APP_VERSION baked in at build time
- Add .gitea/workflows/ci.yml (lint + test with postgres/valkey services)
- Add .gitea/workflows/build.yml (version bump, build, push to registry)
- Update CLAUDE.md and docs/architecture.md to remove multi-tenancy
- Add docs/deployment.md covering DOKS + ArgoCD architecture

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ryan Moon
2026-04-01 19:50:37 -05:00
parent ffef4c8727
commit c2b1073fef
15 changed files with 419 additions and 26 deletions

View File

@@ -1,4 +1,7 @@
import Fastify from 'fastify'
import { drizzle } from 'drizzle-orm/postgres-js'
import { migrate } from 'drizzle-orm/postgres-js/migrator'
import postgres from 'postgres'
import rateLimit from '@fastify/rate-limit'
import { databasePlugin } from './plugins/database.js'
import { redisPlugin } from './plugins/redis.js'
@@ -8,6 +11,7 @@ import { authPlugin } from './plugins/auth.js'
import { devAuthPlugin } from './plugins/dev-auth.js'
import { storagePlugin } from './plugins/storage.js'
import { healthRoutes } from './routes/v1/health.js'
import { versionRoutes } from './routes/v1/version.js'
import { authRoutes } from './routes/v1/auth.js'
import { accountRoutes } from './routes/v1/accounts.js'
import { inventoryRoutes } from './routes/v1/inventory.js'
@@ -92,6 +96,7 @@ export async function buildApp() {
// Core routes — always available
await app.register(healthRoutes, { prefix: '/v1' })
await app.register(versionRoutes, { prefix: '/v1' })
await app.register(authRoutes, { prefix: '/v1' })
await app.register(accountRoutes, { prefix: '/v1' })
await app.register(rbacRoutes, { prefix: '/v1' })
@@ -138,7 +143,20 @@ export async function buildApp() {
return app
}
async function runMigrations() {
const connectionString = process.env.DATABASE_URL
if (!connectionString) throw new Error('DATABASE_URL is required')
const migrationsFolder = process.env.MIGRATIONS_DIR ?? './src/db/migrations'
const sql = postgres(connectionString, { max: 1 })
const db = drizzle(sql)
await migrate(db, { migrationsFolder })
await sql.end()
}
async function start() {
await runMigrations()
const app = await buildApp()
const port = parseInt(process.env.PORT ?? '8000', 10)