@kognitivedev/deployer-docker 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +262 -0
- package/package.json +27 -0
- package/src/index.ts +277 -0
- package/tsconfig.json +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @kognitivedev/deployer-docker
|
|
2
|
+
|
|
3
|
+
> Generate Docker deployment files for Kognitive projects.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { generateDockerFiles } from "@kognitivedev/deployer-docker";
|
|
9
|
+
|
|
10
|
+
generateDockerFiles("./", {
|
|
11
|
+
backendPort: 3001,
|
|
12
|
+
dashboardPort: 3002,
|
|
13
|
+
includeRedis: true,
|
|
14
|
+
includeQdrant: true,
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Generates: `Dockerfile`, `docker-compose.prod.yml`, `.dockerignore`, `.env.example`
|
|
19
|
+
|
|
20
|
+
The Dockerfile includes a non-root user, health check against `/api/runtime/info`, and optimized layer caching.
|
|
21
|
+
|
|
22
|
+
## CI/CD with GitHub Actions
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { generateGitHubActionsDocker } from "@kognitivedev/deployer-docker";
|
|
26
|
+
|
|
27
|
+
generateGitHubActionsDocker("./", {
|
|
28
|
+
registry: "ghcr.io", // or "docker.io"
|
|
29
|
+
imageName: "my-app",
|
|
30
|
+
triggerBranch: "main",
|
|
31
|
+
});
|
|
32
|
+
// Generates: .github/workflows/docker-deploy.yml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Deployment Script
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { generateDeployScript } from "@kognitivedev/deployer-docker";
|
|
39
|
+
|
|
40
|
+
generateDeployScript("./");
|
|
41
|
+
// Generates: deploy.sh (docker compose up + health check + rollback on failure)
|
|
42
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Docker deployment files for a Kognitive project.
|
|
3
|
+
*/
|
|
4
|
+
export declare function generateDockerFiles(projectDir: string, options?: {
|
|
5
|
+
backendPort?: number;
|
|
6
|
+
dashboardPort?: number;
|
|
7
|
+
includeRedis?: boolean;
|
|
8
|
+
includeQdrant?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
/**
|
|
11
|
+
* Generate GitHub Actions workflow for Docker-based deployment.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateGitHubActionsDocker(projectDir: string, options?: {
|
|
14
|
+
registry?: string;
|
|
15
|
+
imageName?: string;
|
|
16
|
+
triggerBranch?: string;
|
|
17
|
+
}): void;
|
|
18
|
+
/**
|
|
19
|
+
* Generate a deployment script with health checking and rollback.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateDeployScript(projectDir: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Generate .env.example template content.
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateEnvTemplate(): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateDockerFiles = generateDockerFiles;
|
|
4
|
+
exports.generateGitHubActionsDocker = generateGitHubActionsDocker;
|
|
5
|
+
exports.generateDeployScript = generateDeployScript;
|
|
6
|
+
exports.generateEnvTemplate = generateEnvTemplate;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
/**
|
|
10
|
+
* Generate Docker deployment files for a Kognitive project.
|
|
11
|
+
*/
|
|
12
|
+
function generateDockerFiles(projectDir, options) {
|
|
13
|
+
var _a, _b;
|
|
14
|
+
const backendPort = (_a = options === null || options === void 0 ? void 0 : options.backendPort) !== null && _a !== void 0 ? _a : 3001;
|
|
15
|
+
const dashboardPort = (_b = options === null || options === void 0 ? void 0 : options.dashboardPort) !== null && _b !== void 0 ? _b : 3002;
|
|
16
|
+
// Dockerfile — improved multi-stage with non-root user and health check
|
|
17
|
+
const dockerfile = `FROM oven/bun:1.2 AS base
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
|
|
20
|
+
# Install dependencies first for better layer caching
|
|
21
|
+
COPY package.json bun.lock* ./
|
|
22
|
+
RUN bun install --frozen-lockfile
|
|
23
|
+
|
|
24
|
+
# Copy source and build
|
|
25
|
+
COPY . .
|
|
26
|
+
RUN bun run build
|
|
27
|
+
|
|
28
|
+
# Production
|
|
29
|
+
FROM oven/bun:1.2-slim
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
|
|
32
|
+
# Create non-root user
|
|
33
|
+
RUN addgroup --system --gid 1001 kognitive && \\
|
|
34
|
+
adduser --system --uid 1001 kognitive
|
|
35
|
+
|
|
36
|
+
COPY --from=base --chown=kognitive:kognitive /app ./
|
|
37
|
+
|
|
38
|
+
USER kognitive
|
|
39
|
+
|
|
40
|
+
EXPOSE ${backendPort} ${dashboardPort}
|
|
41
|
+
|
|
42
|
+
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \\
|
|
43
|
+
CMD curl -f http://localhost:${backendPort}/api/runtime/info || exit 1
|
|
44
|
+
|
|
45
|
+
CMD ["bun", "run", "start"]
|
|
46
|
+
`;
|
|
47
|
+
// docker-compose.yml
|
|
48
|
+
const services = [
|
|
49
|
+
` app:
|
|
50
|
+
build: .
|
|
51
|
+
ports:
|
|
52
|
+
- "\${BACKEND_PORT:-${backendPort}}:${backendPort}"
|
|
53
|
+
- "\${DASHBOARD_PORT:-${dashboardPort}}:${dashboardPort}"
|
|
54
|
+
env_file:
|
|
55
|
+
- .env
|
|
56
|
+
environment:
|
|
57
|
+
- DATABASE_URL=postgres://postgres:\${POSTGRES_PASSWORD:-password}@db:5432/kognitive
|
|
58
|
+
- NODE_ENV=production
|
|
59
|
+
depends_on:
|
|
60
|
+
db:
|
|
61
|
+
condition: service_healthy
|
|
62
|
+
restart: unless-stopped`,
|
|
63
|
+
` db:
|
|
64
|
+
image: pgvector/pgvector:pg16
|
|
65
|
+
environment:
|
|
66
|
+
POSTGRES_USER: postgres
|
|
67
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-password}
|
|
68
|
+
POSTGRES_DB: kognitive
|
|
69
|
+
volumes:
|
|
70
|
+
- postgres_data:/var/lib/postgresql/data
|
|
71
|
+
healthcheck:
|
|
72
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
73
|
+
interval: 10s
|
|
74
|
+
timeout: 5s
|
|
75
|
+
retries: 5
|
|
76
|
+
restart: unless-stopped`,
|
|
77
|
+
];
|
|
78
|
+
if (options === null || options === void 0 ? void 0 : options.includeRedis) {
|
|
79
|
+
services.push(` redis:
|
|
80
|
+
image: redis:7-alpine
|
|
81
|
+
volumes:
|
|
82
|
+
- redis_data:/data
|
|
83
|
+
healthcheck:
|
|
84
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
85
|
+
interval: 10s
|
|
86
|
+
timeout: 5s
|
|
87
|
+
retries: 5
|
|
88
|
+
restart: unless-stopped`);
|
|
89
|
+
}
|
|
90
|
+
if (options === null || options === void 0 ? void 0 : options.includeQdrant) {
|
|
91
|
+
services.push(` qdrant:
|
|
92
|
+
image: qdrant/qdrant:latest
|
|
93
|
+
ports:
|
|
94
|
+
- "6333:6333"
|
|
95
|
+
volumes:
|
|
96
|
+
- qdrant_data:/qdrant/storage
|
|
97
|
+
restart: unless-stopped`);
|
|
98
|
+
}
|
|
99
|
+
const volumes = [" postgres_data:"];
|
|
100
|
+
if (options === null || options === void 0 ? void 0 : options.includeRedis)
|
|
101
|
+
volumes.push(" redis_data:");
|
|
102
|
+
if (options === null || options === void 0 ? void 0 : options.includeQdrant)
|
|
103
|
+
volumes.push(" qdrant_data:");
|
|
104
|
+
const compose = `version: '3.8'
|
|
105
|
+
|
|
106
|
+
services:
|
|
107
|
+
${services.join("\n\n")}
|
|
108
|
+
|
|
109
|
+
volumes:
|
|
110
|
+
${volumes.join("\n")}
|
|
111
|
+
`;
|
|
112
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(projectDir, "Dockerfile"), dockerfile);
|
|
113
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(projectDir, "docker-compose.prod.yml"), compose);
|
|
114
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(projectDir, ".dockerignore"), "node_modules\n.next\ndist\n.env\n.env.*\n.git\n*.log\n");
|
|
115
|
+
// Generate .env.example
|
|
116
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(projectDir, ".env.example"), generateEnvTemplate());
|
|
117
|
+
console.log(" Generated Docker deployment files:");
|
|
118
|
+
console.log(" Dockerfile");
|
|
119
|
+
console.log(" docker-compose.prod.yml");
|
|
120
|
+
console.log(" .dockerignore");
|
|
121
|
+
console.log(" .env.example");
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate GitHub Actions workflow for Docker-based deployment.
|
|
125
|
+
*/
|
|
126
|
+
function generateGitHubActionsDocker(projectDir, options) {
|
|
127
|
+
var _a, _b, _c;
|
|
128
|
+
const registry = (_a = options === null || options === void 0 ? void 0 : options.registry) !== null && _a !== void 0 ? _a : "ghcr.io";
|
|
129
|
+
const imageName = (_b = options === null || options === void 0 ? void 0 : options.imageName) !== null && _b !== void 0 ? _b : "kognitive-app";
|
|
130
|
+
const branch = (_c = options === null || options === void 0 ? void 0 : options.triggerBranch) !== null && _c !== void 0 ? _c : "main";
|
|
131
|
+
const workflow = `name: Build and Deploy (Docker)
|
|
132
|
+
|
|
133
|
+
on:
|
|
134
|
+
push:
|
|
135
|
+
branches: [${branch}]
|
|
136
|
+
|
|
137
|
+
env:
|
|
138
|
+
REGISTRY: ${registry}
|
|
139
|
+
IMAGE_NAME: \${{ github.repository }}/${imageName}
|
|
140
|
+
|
|
141
|
+
jobs:
|
|
142
|
+
build:
|
|
143
|
+
runs-on: ubuntu-latest
|
|
144
|
+
permissions:
|
|
145
|
+
contents: read
|
|
146
|
+
packages: write
|
|
147
|
+
|
|
148
|
+
steps:
|
|
149
|
+
- uses: actions/checkout@v4
|
|
150
|
+
|
|
151
|
+
- name: Log in to Container Registry
|
|
152
|
+
uses: docker/login-action@v3
|
|
153
|
+
with:
|
|
154
|
+
registry: \${{ env.REGISTRY }}
|
|
155
|
+
username: \${{ github.actor }}
|
|
156
|
+
password: \${{ secrets.GITHUB_TOKEN }}
|
|
157
|
+
|
|
158
|
+
- name: Build and push Docker image
|
|
159
|
+
uses: docker/build-push-action@v5
|
|
160
|
+
with:
|
|
161
|
+
context: .
|
|
162
|
+
push: true
|
|
163
|
+
tags: |
|
|
164
|
+
\${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:latest
|
|
165
|
+
\${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:\${{ github.sha }}
|
|
166
|
+
cache-from: type=gha
|
|
167
|
+
cache-to: type=gha,mode=max
|
|
168
|
+
|
|
169
|
+
deploy:
|
|
170
|
+
needs: build
|
|
171
|
+
runs-on: ubuntu-latest
|
|
172
|
+
if: github.ref == 'refs/heads/${branch}'
|
|
173
|
+
|
|
174
|
+
steps:
|
|
175
|
+
- name: Deploy to server
|
|
176
|
+
run: |
|
|
177
|
+
echo "Add your deployment commands here."
|
|
178
|
+
echo "Options:"
|
|
179
|
+
echo " - SSH into server and run docker compose pull && docker compose up -d"
|
|
180
|
+
echo " - Use a webhook to trigger deployment"
|
|
181
|
+
echo " - Use a deployment service like Watchtower"
|
|
182
|
+
`;
|
|
183
|
+
const dir = (0, path_1.join)(projectDir, ".github", "workflows");
|
|
184
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
185
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
186
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(dir, "docker-deploy.yml"), workflow);
|
|
187
|
+
console.log(" Generated GitHub Actions workflow:");
|
|
188
|
+
console.log(" .github/workflows/docker-deploy.yml");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Generate a deployment script with health checking and rollback.
|
|
192
|
+
*/
|
|
193
|
+
function generateDeployScript(projectDir) {
|
|
194
|
+
const script = `#!/bin/bash
|
|
195
|
+
set -euo pipefail
|
|
196
|
+
|
|
197
|
+
echo "=== Kognitive Deployment ==="
|
|
198
|
+
|
|
199
|
+
# Pull latest images
|
|
200
|
+
echo "Pulling latest images..."
|
|
201
|
+
docker compose -f docker-compose.prod.yml pull
|
|
202
|
+
|
|
203
|
+
# Store current image for rollback
|
|
204
|
+
PREV_IMAGE=$(docker compose -f docker-compose.prod.yml images app -q 2>/dev/null || echo "")
|
|
205
|
+
|
|
206
|
+
# Start services
|
|
207
|
+
echo "Starting services..."
|
|
208
|
+
docker compose -f docker-compose.prod.yml up -d
|
|
209
|
+
|
|
210
|
+
# Health check
|
|
211
|
+
echo "Waiting for health check..."
|
|
212
|
+
MAX_RETRIES=30
|
|
213
|
+
RETRY=0
|
|
214
|
+
while [ $RETRY -lt $MAX_RETRIES ]; do
|
|
215
|
+
if curl -sf http://localhost:3001/api/runtime/info > /dev/null 2>&1; then
|
|
216
|
+
echo "Health check passed!"
|
|
217
|
+
exit 0
|
|
218
|
+
fi
|
|
219
|
+
RETRY=$((RETRY + 1))
|
|
220
|
+
echo " Attempt $RETRY/$MAX_RETRIES..."
|
|
221
|
+
sleep 2
|
|
222
|
+
done
|
|
223
|
+
|
|
224
|
+
# Health check failed — rollback
|
|
225
|
+
echo "Health check failed! Rolling back..."
|
|
226
|
+
if [ -n "$PREV_IMAGE" ]; then
|
|
227
|
+
docker compose -f docker-compose.prod.yml down
|
|
228
|
+
echo "Rollback: restart with previous image"
|
|
229
|
+
docker compose -f docker-compose.prod.yml up -d
|
|
230
|
+
fi
|
|
231
|
+
exit 1
|
|
232
|
+
`;
|
|
233
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(projectDir, "deploy.sh"), script, { mode: 0o755 });
|
|
234
|
+
console.log(" Generated deployment script:");
|
|
235
|
+
console.log(" deploy.sh");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Generate .env.example template content.
|
|
239
|
+
*/
|
|
240
|
+
function generateEnvTemplate() {
|
|
241
|
+
return `# ── Required ──
|
|
242
|
+
DATABASE_URL=postgres://postgres:password@localhost:5432/kognitive
|
|
243
|
+
RUNTIME_API_KEY=your-runtime-api-key
|
|
244
|
+
|
|
245
|
+
# ── Authentication (Clerk) ──
|
|
246
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
|
|
247
|
+
CLERK_SECRET_KEY=
|
|
248
|
+
|
|
249
|
+
# ── Optional ──
|
|
250
|
+
# REDIS_URL=redis://localhost:6379
|
|
251
|
+
# QDRANT_URL=http://localhost:6333
|
|
252
|
+
|
|
253
|
+
# ── AI Providers ──
|
|
254
|
+
# OPENAI_API_KEY=
|
|
255
|
+
# ANTHROPIC_API_KEY=
|
|
256
|
+
# OPENROUTER_API_KEY=
|
|
257
|
+
|
|
258
|
+
# ── Deployment ──
|
|
259
|
+
# NODE_ENV=production
|
|
260
|
+
# POSTGRES_PASSWORD=change-me-in-production
|
|
261
|
+
`;
|
|
262
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kognitivedev/deployer-docker",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"dev": "tsc -w"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"typescript": "^5.0.0",
|
|
12
|
+
"@types/node": "^20.0.0"
|
|
13
|
+
},
|
|
14
|
+
"description": "Docker deployment file generator for Kognitive",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"kognitive",
|
|
17
|
+
"docker",
|
|
18
|
+
"deployment"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/kognitivedev/kognitive",
|
|
24
|
+
"directory": "packages/deployer-docker"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://kognitive.dev"
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate Docker deployment files for a Kognitive project.
|
|
6
|
+
*/
|
|
7
|
+
export function generateDockerFiles(projectDir: string, options?: {
|
|
8
|
+
backendPort?: number;
|
|
9
|
+
dashboardPort?: number;
|
|
10
|
+
includeRedis?: boolean;
|
|
11
|
+
includeQdrant?: boolean;
|
|
12
|
+
}) {
|
|
13
|
+
const backendPort = options?.backendPort ?? 3001;
|
|
14
|
+
const dashboardPort = options?.dashboardPort ?? 3002;
|
|
15
|
+
|
|
16
|
+
// Dockerfile — improved multi-stage with non-root user and health check
|
|
17
|
+
const dockerfile = `FROM oven/bun:1.2 AS base
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
|
|
20
|
+
# Install dependencies first for better layer caching
|
|
21
|
+
COPY package.json bun.lock* ./
|
|
22
|
+
RUN bun install --frozen-lockfile
|
|
23
|
+
|
|
24
|
+
# Copy source and build
|
|
25
|
+
COPY . .
|
|
26
|
+
RUN bun run build
|
|
27
|
+
|
|
28
|
+
# Production
|
|
29
|
+
FROM oven/bun:1.2-slim
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
|
|
32
|
+
# Create non-root user
|
|
33
|
+
RUN addgroup --system --gid 1001 kognitive && \\
|
|
34
|
+
adduser --system --uid 1001 kognitive
|
|
35
|
+
|
|
36
|
+
COPY --from=base --chown=kognitive:kognitive /app ./
|
|
37
|
+
|
|
38
|
+
USER kognitive
|
|
39
|
+
|
|
40
|
+
EXPOSE ${backendPort} ${dashboardPort}
|
|
41
|
+
|
|
42
|
+
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \\
|
|
43
|
+
CMD curl -f http://localhost:${backendPort}/api/runtime/info || exit 1
|
|
44
|
+
|
|
45
|
+
CMD ["bun", "run", "start"]
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
// docker-compose.yml
|
|
49
|
+
const services: string[] = [
|
|
50
|
+
` app:
|
|
51
|
+
build: .
|
|
52
|
+
ports:
|
|
53
|
+
- "\${BACKEND_PORT:-${backendPort}}:${backendPort}"
|
|
54
|
+
- "\${DASHBOARD_PORT:-${dashboardPort}}:${dashboardPort}"
|
|
55
|
+
env_file:
|
|
56
|
+
- .env
|
|
57
|
+
environment:
|
|
58
|
+
- DATABASE_URL=postgres://postgres:\${POSTGRES_PASSWORD:-password}@db:5432/kognitive
|
|
59
|
+
- NODE_ENV=production
|
|
60
|
+
depends_on:
|
|
61
|
+
db:
|
|
62
|
+
condition: service_healthy
|
|
63
|
+
restart: unless-stopped`,
|
|
64
|
+
` db:
|
|
65
|
+
image: pgvector/pgvector:pg16
|
|
66
|
+
environment:
|
|
67
|
+
POSTGRES_USER: postgres
|
|
68
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-password}
|
|
69
|
+
POSTGRES_DB: kognitive
|
|
70
|
+
volumes:
|
|
71
|
+
- postgres_data:/var/lib/postgresql/data
|
|
72
|
+
healthcheck:
|
|
73
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
74
|
+
interval: 10s
|
|
75
|
+
timeout: 5s
|
|
76
|
+
retries: 5
|
|
77
|
+
restart: unless-stopped`,
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
if (options?.includeRedis) {
|
|
81
|
+
services.push(` redis:
|
|
82
|
+
image: redis:7-alpine
|
|
83
|
+
volumes:
|
|
84
|
+
- redis_data:/data
|
|
85
|
+
healthcheck:
|
|
86
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
87
|
+
interval: 10s
|
|
88
|
+
timeout: 5s
|
|
89
|
+
retries: 5
|
|
90
|
+
restart: unless-stopped`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options?.includeQdrant) {
|
|
94
|
+
services.push(` qdrant:
|
|
95
|
+
image: qdrant/qdrant:latest
|
|
96
|
+
ports:
|
|
97
|
+
- "6333:6333"
|
|
98
|
+
volumes:
|
|
99
|
+
- qdrant_data:/qdrant/storage
|
|
100
|
+
restart: unless-stopped`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const volumes = [" postgres_data:"];
|
|
104
|
+
if (options?.includeRedis) volumes.push(" redis_data:");
|
|
105
|
+
if (options?.includeQdrant) volumes.push(" qdrant_data:");
|
|
106
|
+
|
|
107
|
+
const compose = `version: '3.8'
|
|
108
|
+
|
|
109
|
+
services:
|
|
110
|
+
${services.join("\n\n")}
|
|
111
|
+
|
|
112
|
+
volumes:
|
|
113
|
+
${volumes.join("\n")}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
writeFileSync(join(projectDir, "Dockerfile"), dockerfile);
|
|
117
|
+
writeFileSync(join(projectDir, "docker-compose.prod.yml"), compose);
|
|
118
|
+
writeFileSync(join(projectDir, ".dockerignore"), "node_modules\n.next\ndist\n.env\n.env.*\n.git\n*.log\n");
|
|
119
|
+
|
|
120
|
+
// Generate .env.example
|
|
121
|
+
writeFileSync(join(projectDir, ".env.example"), generateEnvTemplate());
|
|
122
|
+
|
|
123
|
+
console.log(" Generated Docker deployment files:");
|
|
124
|
+
console.log(" Dockerfile");
|
|
125
|
+
console.log(" docker-compose.prod.yml");
|
|
126
|
+
console.log(" .dockerignore");
|
|
127
|
+
console.log(" .env.example");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generate GitHub Actions workflow for Docker-based deployment.
|
|
132
|
+
*/
|
|
133
|
+
export function generateGitHubActionsDocker(projectDir: string, options?: {
|
|
134
|
+
registry?: string;
|
|
135
|
+
imageName?: string;
|
|
136
|
+
triggerBranch?: string;
|
|
137
|
+
}) {
|
|
138
|
+
const registry = options?.registry ?? "ghcr.io";
|
|
139
|
+
const imageName = options?.imageName ?? "kognitive-app";
|
|
140
|
+
const branch = options?.triggerBranch ?? "main";
|
|
141
|
+
|
|
142
|
+
const workflow = `name: Build and Deploy (Docker)
|
|
143
|
+
|
|
144
|
+
on:
|
|
145
|
+
push:
|
|
146
|
+
branches: [${branch}]
|
|
147
|
+
|
|
148
|
+
env:
|
|
149
|
+
REGISTRY: ${registry}
|
|
150
|
+
IMAGE_NAME: \${{ github.repository }}/${imageName}
|
|
151
|
+
|
|
152
|
+
jobs:
|
|
153
|
+
build:
|
|
154
|
+
runs-on: ubuntu-latest
|
|
155
|
+
permissions:
|
|
156
|
+
contents: read
|
|
157
|
+
packages: write
|
|
158
|
+
|
|
159
|
+
steps:
|
|
160
|
+
- uses: actions/checkout@v4
|
|
161
|
+
|
|
162
|
+
- name: Log in to Container Registry
|
|
163
|
+
uses: docker/login-action@v3
|
|
164
|
+
with:
|
|
165
|
+
registry: \${{ env.REGISTRY }}
|
|
166
|
+
username: \${{ github.actor }}
|
|
167
|
+
password: \${{ secrets.GITHUB_TOKEN }}
|
|
168
|
+
|
|
169
|
+
- name: Build and push Docker image
|
|
170
|
+
uses: docker/build-push-action@v5
|
|
171
|
+
with:
|
|
172
|
+
context: .
|
|
173
|
+
push: true
|
|
174
|
+
tags: |
|
|
175
|
+
\${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:latest
|
|
176
|
+
\${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:\${{ github.sha }}
|
|
177
|
+
cache-from: type=gha
|
|
178
|
+
cache-to: type=gha,mode=max
|
|
179
|
+
|
|
180
|
+
deploy:
|
|
181
|
+
needs: build
|
|
182
|
+
runs-on: ubuntu-latest
|
|
183
|
+
if: github.ref == 'refs/heads/${branch}'
|
|
184
|
+
|
|
185
|
+
steps:
|
|
186
|
+
- name: Deploy to server
|
|
187
|
+
run: |
|
|
188
|
+
echo "Add your deployment commands here."
|
|
189
|
+
echo "Options:"
|
|
190
|
+
echo " - SSH into server and run docker compose pull && docker compose up -d"
|
|
191
|
+
echo " - Use a webhook to trigger deployment"
|
|
192
|
+
echo " - Use a deployment service like Watchtower"
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
const dir = join(projectDir, ".github", "workflows");
|
|
196
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
197
|
+
writeFileSync(join(dir, "docker-deploy.yml"), workflow);
|
|
198
|
+
|
|
199
|
+
console.log(" Generated GitHub Actions workflow:");
|
|
200
|
+
console.log(" .github/workflows/docker-deploy.yml");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Generate a deployment script with health checking and rollback.
|
|
205
|
+
*/
|
|
206
|
+
export function generateDeployScript(projectDir: string) {
|
|
207
|
+
const script = `#!/bin/bash
|
|
208
|
+
set -euo pipefail
|
|
209
|
+
|
|
210
|
+
echo "=== Kognitive Deployment ==="
|
|
211
|
+
|
|
212
|
+
# Pull latest images
|
|
213
|
+
echo "Pulling latest images..."
|
|
214
|
+
docker compose -f docker-compose.prod.yml pull
|
|
215
|
+
|
|
216
|
+
# Store current image for rollback
|
|
217
|
+
PREV_IMAGE=$(docker compose -f docker-compose.prod.yml images app -q 2>/dev/null || echo "")
|
|
218
|
+
|
|
219
|
+
# Start services
|
|
220
|
+
echo "Starting services..."
|
|
221
|
+
docker compose -f docker-compose.prod.yml up -d
|
|
222
|
+
|
|
223
|
+
# Health check
|
|
224
|
+
echo "Waiting for health check..."
|
|
225
|
+
MAX_RETRIES=30
|
|
226
|
+
RETRY=0
|
|
227
|
+
while [ $RETRY -lt $MAX_RETRIES ]; do
|
|
228
|
+
if curl -sf http://localhost:3001/api/runtime/info > /dev/null 2>&1; then
|
|
229
|
+
echo "Health check passed!"
|
|
230
|
+
exit 0
|
|
231
|
+
fi
|
|
232
|
+
RETRY=$((RETRY + 1))
|
|
233
|
+
echo " Attempt $RETRY/$MAX_RETRIES..."
|
|
234
|
+
sleep 2
|
|
235
|
+
done
|
|
236
|
+
|
|
237
|
+
# Health check failed — rollback
|
|
238
|
+
echo "Health check failed! Rolling back..."
|
|
239
|
+
if [ -n "$PREV_IMAGE" ]; then
|
|
240
|
+
docker compose -f docker-compose.prod.yml down
|
|
241
|
+
echo "Rollback: restart with previous image"
|
|
242
|
+
docker compose -f docker-compose.prod.yml up -d
|
|
243
|
+
fi
|
|
244
|
+
exit 1
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
writeFileSync(join(projectDir, "deploy.sh"), script, { mode: 0o755 });
|
|
248
|
+
console.log(" Generated deployment script:");
|
|
249
|
+
console.log(" deploy.sh");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Generate .env.example template content.
|
|
254
|
+
*/
|
|
255
|
+
export function generateEnvTemplate(): string {
|
|
256
|
+
return `# ── Required ──
|
|
257
|
+
DATABASE_URL=postgres://postgres:password@localhost:5432/kognitive
|
|
258
|
+
RUNTIME_API_KEY=your-runtime-api-key
|
|
259
|
+
|
|
260
|
+
# ── Authentication (Clerk) ──
|
|
261
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
|
|
262
|
+
CLERK_SECRET_KEY=
|
|
263
|
+
|
|
264
|
+
# ── Optional ──
|
|
265
|
+
# REDIS_URL=redis://localhost:6379
|
|
266
|
+
# QDRANT_URL=http://localhost:6333
|
|
267
|
+
|
|
268
|
+
# ── AI Providers ──
|
|
269
|
+
# OPENAI_API_KEY=
|
|
270
|
+
# ANTHROPIC_API_KEY=
|
|
271
|
+
# OPENROUTER_API_KEY=
|
|
272
|
+
|
|
273
|
+
# ── Deployment ──
|
|
274
|
+
# NODE_ENV=production
|
|
275
|
+
# POSTGRES_PASSWORD=change-me-in-production
|
|
276
|
+
`;
|
|
277
|
+
}
|