Skip to content

Commit 42f24d8

Browse files
feat: add Docker support and health monitoring
- Add Dockerfiles for API and web applications with multi-stage builds - Create docker-compose.yaml for local development with Redis, API, and web services - Replace basic health endpoint with comprehensive health monitoring using @nestjs/terminus - Add structured logging with pino and nestjs-pino - Include nginx configuration for web application routing - Update CI workflow to validate Docker builds - Add .env.example with configuration template - Update dependencies to support new features
1 parent 8fc1b02 commit 42f24d8

13 files changed

Lines changed: 498 additions & 14 deletions

File tree

.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Configuração Twitch
2+
TWITCH_CLIENT_ID=seu_client_id_aqui
3+
TWITCH_CLIENT_SECRET=seu_client_secret_aqui
4+
5+
# Configuração Backend
6+
PORT=3000
7+
LEGACY_PORT=3001
8+
NODE_ENV=development
9+
10+
# Configuração Redis
11+
REDIS_URL=redis://localhost:6379

.github/workflows/ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,17 @@ jobs:
3535

3636
- name: Build workspace
3737
run: pnpm build
38+
39+
docker-build:
40+
runs-on: ubuntu-latest
41+
needs: build-and-lint
42+
43+
steps:
44+
- name: Checkout code
45+
uses: actions/checkout@v4
46+
47+
- name: Set up Docker Buildx
48+
uses: docker/setup-buildx-action@v3
49+
50+
- name: Validate Docker Compose build
51+
run: docker compose build

apps/api/Dockerfile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Etapa 1: Build
2+
FROM node:22-alpine AS build
3+
4+
# Configurar pnpm
5+
ENV PNPM_HOME="/pnpm"
6+
ENV PATH="$PNPM_HOME:$PATH"
7+
RUN corepack enable
8+
9+
WORKDIR /app
10+
11+
# Copiar os arquivos essenciais para o workspace e root package.json
12+
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
13+
COPY packages ./packages
14+
COPY apps/api ./apps/api
15+
16+
# Instalar as dependências do monorepo e buildar o projeto e os packages
17+
RUN pnpm install --frozen-lockfile
18+
RUN pnpm --filter @twitch-chat-visualizer/shared build
19+
RUN pnpm --filter @twitch-chat-visualizer/api build
20+
21+
# Etapa 2: Produção
22+
FROM node:22-alpine AS production
23+
24+
ENV PNPM_HOME="/pnpm"
25+
ENV PATH="$PNPM_HOME:$PATH"
26+
RUN corepack enable
27+
28+
WORKDIR /app
29+
30+
ENV NODE_ENV=production
31+
ENV PORT=3000
32+
33+
# Copiar os arquivos de lock e workspaces
34+
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
35+
COPY packages ./packages
36+
COPY --from=build /app/packages/shared/dist ./packages/shared/dist
37+
38+
# Copiar apenas os arquivos do API já compilados e o package.json
39+
COPY apps/api/package.json ./apps/api/
40+
COPY --from=build /app/apps/api/dist ./apps/api/dist
41+
42+
# Instalar apenas dependências de produção para rodar as dists
43+
RUN pnpm install --prod --frozen-lockfile
44+
45+
EXPOSE 3000
46+
47+
CMD ["node", "apps/api/dist/main.js"]

apps/api/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@
2020
"@nestjs/platform-fastify": "^11.0.0",
2121
"@nestjs/platform-socket.io": "^11.1.19",
2222
"@nestjs/schedule": "^6.1.3",
23+
"@nestjs/terminus": "^11.1.1",
2324
"@nestjs/websockets": "^11.1.19",
2425
"@twitch-chat-visualizer/shared": "workspace:*",
2526
"axios": "^0.21.4",
2627
"cache-manager": "^7.2.8",
2728
"cache-manager-redis-yet": "^5.1.5",
2829
"fastify": "^5.0.0",
30+
"nestjs-pino": "^4.6.1",
31+
"pino-http": "^11.0.0",
32+
"pino-pretty": "^13.1.3",
33+
"qs": "^6.15.1",
2934
"reflect-metadata": "^0.2.0",
3035
"rxjs": "^7.8.1",
3136
"socket.io": "^3.1.2",
@@ -35,6 +40,7 @@
3540
"@nestjs/cli": "^11.0.0",
3641
"@twitch-chat-visualizer/config-ts": "workspace:*",
3742
"@types/node": "^22.0.0",
43+
"@types/qs": "^6.15.0",
3844
"@types/tmi.js": "^1.8.6",
3945
"@vitest/ui": "^4.1.5",
4046
"socket.io-client": "^4.8.3",

apps/api/src/app.controller.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

apps/api/src/app.module.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,24 @@ import { ConfigModule } from '@nestjs/config';
33
import { ScheduleModule } from '@nestjs/schedule';
44
import { CacheModule } from '@nestjs/cache-manager';
55
import { redisStore } from 'cache-manager-redis-yet';
6-
import { AppController } from './app.controller';
6+
import { LoggerModule } from 'nestjs-pino';
77
import { AssetsController } from './controllers/assets.controller';
88
import { AuthModule } from './auth/auth.module';
99
import { CacheExtModule } from './cache/cache.module';
1010
import { TwitchModule } from './twitch/twitch.module';
1111
import { GatewaysModule } from './gateways/gateways.module';
12+
import { HealthModule } from './health/health.module';
1213

1314
@Module({
1415
imports: [
16+
LoggerModule.forRoot({
17+
pinoHttp: {
18+
transport:
19+
process.env.NODE_ENV !== 'production'
20+
? { target: 'pino-pretty', options: { singleLine: true } }
21+
: undefined,
22+
},
23+
}),
1524
ConfigModule.forRoot({
1625
isGlobal: true,
1726
envFilePath: '../../.env', // Pode ler da raiz do monorepo se desejar
@@ -38,8 +47,9 @@ import { GatewaysModule } from './gateways/gateways.module';
3847
CacheExtModule,
3948
TwitchModule,
4049
GatewaysModule,
50+
HealthModule,
4151
],
42-
controllers: [AppController, AssetsController],
52+
controllers: [AssetsController],
4353
providers: [],
4454
})
4555
export class AppModule {}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Controller, Get, Inject } from '@nestjs/common';
2+
import { HealthCheckService, MemoryHealthIndicator, HealthCheck, HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';
3+
import { CACHE_MANAGER } from '@nestjs/cache-manager';
4+
import { Cache } from 'cache-manager';
5+
6+
class RedisHealthIndicator extends HealthIndicator {
7+
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {
8+
super();
9+
}
10+
11+
async isHealthy(key: string): Promise<HealthIndicatorResult> {
12+
try {
13+
await this.cacheManager.set('health-check-ping', 'pong', 1000);
14+
const res = await this.cacheManager.get('health-check-ping');
15+
const isHealthy = res === 'pong';
16+
17+
const result = this.getStatus(key, isHealthy, { message: 'Redis/Cache is responding' });
18+
19+
if (isHealthy) {
20+
return result;
21+
}
22+
throw new HealthCheckError('Redischeck failed', result);
23+
} catch (e: any) {
24+
throw new HealthCheckError('Redischeck failed', this.getStatus(key, false, { message: e.message }));
25+
}
26+
}
27+
}
28+
29+
@Controller('health')
30+
export class HealthController {
31+
constructor(
32+
private health: HealthCheckService,
33+
private memory: MemoryHealthIndicator,
34+
@Inject(CACHE_MANAGER) private cacheManager: Cache,
35+
) {}
36+
37+
@Get()
38+
@HealthCheck()
39+
check() {
40+
const redisIndicator = new RedisHealthIndicator(this.cacheManager);
41+
42+
return this.health.check([
43+
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024), // 150MB
44+
() => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024), // 300MB
45+
() => redisIndicator.isHealthy('redis'),
46+
]);
47+
}
48+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from '@nestjs/common';
2+
import { TerminusModule } from '@nestjs/terminus';
3+
import { HealthController } from './health.controller';
4+
5+
@Module({
6+
imports: [TerminusModule],
7+
controllers: [HealthController],
8+
})
9+
export class HealthModule {}

apps/api/src/main.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import { NestFactory } from '@nestjs/core';
22
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
33
import { AppModule } from './app.module';
44
import fastifyHttpProxy from '@fastify/http-proxy';
5+
import { Logger } from 'nestjs-pino';
56

67
async function bootstrap() {
78
const app = await NestFactory.create<NestFastifyApplication>(
89
AppModule,
9-
new FastifyAdapter()
10+
new FastifyAdapter(),
11+
{ bufferLogs: true }
1012
);
1113

14+
app.useLogger(app.get(Logger));
15+
1216
const port = process.env.PORT || 3000;
1317
const legacyPort = process.env.LEGACY_PORT || 3001;
1418

@@ -32,8 +36,9 @@ async function bootstrap() {
3236

3337
await app.listen(port, '0.0.0.0');
3438

35-
console.log(`🚀 NestJS API Gateway is running on: http://localhost:${port}`);
36-
console.log(`➡️ Proxying unknown routes to legacy app on port: ${legacyPort}`);
39+
const logger = app.get(Logger);
40+
logger.log(`🚀 NestJS API Gateway is running on: http://localhost:${port}`);
41+
logger.log(`➡️ Proxying unknown routes to legacy app on port: ${legacyPort}`);
3742
}
3843

3944
bootstrap();

apps/web/Dockerfile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Etapa 1: Build
2+
FROM node:22-alpine AS build
3+
4+
# Configurar pnpm
5+
ENV PNPM_HOME="/pnpm"
6+
ENV PATH="$PNPM_HOME:$PATH"
7+
RUN corepack enable
8+
9+
WORKDIR /app
10+
11+
# Copiar workspace root
12+
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
13+
COPY packages ./packages
14+
COPY apps/web ./apps/web
15+
16+
# Instalar dependências e buildar
17+
RUN pnpm install --frozen-lockfile
18+
RUN pnpm --filter @twitch-chat-visualizer/shared build
19+
RUN pnpm --filter @twitch-chat-visualizer/web build
20+
21+
# Etapa 2: Nginx (Produção)
22+
FROM nginx:stable-alpine AS production
23+
24+
# Copiar build gerado
25+
COPY --from=build /app/apps/web/dist /usr/share/nginx/html
26+
27+
# Copiar configuração Nginx
28+
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
29+
30+
EXPOSE 80
31+
32+
CMD ["nginx", "-g", "daemon off;"]

0 commit comments

Comments
 (0)