Skip to content

Commit 8d993ee

Browse files
committed
fix: bootstrap Docker startup and repair sidebar load metric
Run Prisma migrations automatically before the API boots in Docker, avoid the misleading missing-.env warning when the container already has injected environment variables, and document that Docker users no longer need manual database bootstrap commands. Also route the dashboard proxy through an internal API URL for container networking and switch the sidebar system load widget to a real host memory-based metric from the API.
1 parent ff1379e commit 8d993ee

File tree

9 files changed

+60
-12
lines changed

9 files changed

+60
-12
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ docker compose -f infra/docker/docker-compose.yml up -d
285285
```
286286

287287
The Docker stack now requires `DASHBOARD_INTERNAL_API_KEY` and `CREDENTIAL_ENCRYPTION_KEY` in `.env`. The API will refuse to boot without them.
288+
The API container now applies Prisma migrations automatically on startup, so you do not need to run manual `pnpm db:*` commands for the Docker workflow.
288289

289290
### 6️⃣ Access the Application
290291

apps/api/src/controllers/DashboardController.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Request, Response } from 'express';
2+
import os from 'os';
23
import { prisma } from '../database/client';
34
import { configService } from '../services/ConfigService';
45
import { browserService } from '../services/BrowserService';
@@ -27,6 +28,11 @@ export class DashboardController {
2728

2829
const browserStatus = browserService.getStatus();
2930
const runningBrowsersCount = browserStatus.default ? 1 : 0;
31+
const totalMemory = os.totalmem();
32+
const usedMemory = totalMemory - os.freemem();
33+
const memoryLoadPercent = totalMemory > 0
34+
? Math.round((usedMemory / totalMemory) * 100)
35+
: 0;
3036

3137
// Calculate success rate
3238
const failedRateVal = totalJobs > 0
@@ -44,7 +50,8 @@ export class DashboardController {
4450
runningBrowsers: runningBrowsersCount,
4551
maxConcurrency: config.maxConcurrency,
4652
browserHeadless: config.browserHeadless,
47-
proxyEnabled: config.proxyEnabled
53+
proxyEnabled: config.proxyEnabled,
54+
systemLoad: memoryLoadPercent
4855
});
4956
} catch (error) {
5057
console.error('Dashboard Stats Error:', error);

apps/api/src/env.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'path';
2+
import fs from 'fs';
23
import dotenv from 'dotenv';
34
import { fileURLToPath } from 'url';
45

@@ -7,10 +8,24 @@ const __filename = fileURLToPath(import.meta.url);
78
const __dirname = path.dirname(__filename);
89

910
const envPath = path.resolve(__dirname, '..', '..', '..', '.env');
10-
const result = dotenv.config({ path: envPath });
11+
const hasEnvFile = fs.existsSync(envPath);
1112

12-
if (result.error) {
13-
console.warn(`⚠️ Could not load .env from ${envPath}`);
13+
if (hasEnvFile) {
14+
const result = dotenv.config({ path: envPath });
15+
16+
if (result.error) {
17+
console.warn(`⚠️ Could not load .env from ${envPath}`);
18+
} else {
19+
console.log(`✅ Loaded environment from ${envPath}`);
20+
}
1421
} else {
15-
console.log(`✅ Loaded environment from ${envPath}`);
22+
const hasInjectedEnv = Boolean(
23+
process.env.DATABASE_URL ||
24+
process.env.DASHBOARD_INTERNAL_API_KEY ||
25+
process.env.CREDENTIAL_ENCRYPTION_KEY
26+
);
27+
28+
if (!hasInjectedEnv) {
29+
console.warn(`⚠️ Could not load .env from ${envPath}`);
30+
}
1631
}

apps/web/src/app/api/[...path]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextRequest } from 'next/server';
22

3-
const backendApiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
3+
const backendApiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
44
const dashboardInternalApiKey = process.env.DASHBOARD_INTERNAL_API_KEY;
55

66
export const dynamic = 'force-dynamic';

apps/web/src/components/dashboard/Sidebar.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,15 @@ export function Sidebar() {
4141
queryFn: async () => {
4242
const res = await fetch('/api/dashboard/stats');
4343
if (!res.ok) {
44-
return { runningBrowsers: 0, maxConcurrency: 0 };
44+
return { systemLoad: 0 };
4545
}
46-
return res.json().catch(() => ({ runningBrowsers: 0, maxConcurrency: 0 }));
46+
return res.json().catch(() => ({ systemLoad: 0 }));
4747
},
4848
refetchInterval: 5000,
4949
refetchOnWindowFocus: false,
5050
});
5151

52-
const systemLoad = systemStats?.maxConcurrency
53-
? Math.min(100, Math.round(((systemStats.runningBrowsers || 0) / systemStats.maxConcurrency) * 100))
54-
: 0;
52+
const systemLoad = Math.max(0, Math.min(100, Number(systemStats?.systemLoad) || 0));
5553

5654
return (
5755
<aside

docs/docker_setup.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This will start:
2121
3. **HeadlessX Web Dashboard** (Port `3000`)
2222

2323
You can access the dashboard at [http://localhost:3000](http://localhost:3000).
24+
The API container automatically runs `prisma migrate deploy` before booting, so Docker users do not need to apply database migrations manually.
2425

2526
## Configuration
2627

infra/docker/api-entrypoint.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
cd /app/apps/api
5+
6+
MAX_ATTEMPTS="${PRISMA_MIGRATE_MAX_ATTEMPTS:-10}"
7+
ATTEMPT=1
8+
9+
echo "🗄️ Applying Prisma migrations..."
10+
11+
until pnpm exec prisma migrate deploy; do
12+
if [ "$ATTEMPT" -ge "$MAX_ATTEMPTS" ]; then
13+
echo "❌ Prisma migration failed after ${MAX_ATTEMPTS} attempts."
14+
exit 1
15+
fi
16+
17+
echo "⚠️ Prisma migration attempt ${ATTEMPT} failed. Retrying in 3 seconds..."
18+
ATTEMPT=$((ATTEMPT + 1))
19+
sleep 3
20+
done
21+
22+
echo "✅ Prisma migrations applied."
23+
exec pnpm exec tsx src/server_entry.ts

infra/docker/api.Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ COPY nx.json ./
3434

3535
# Copy API app
3636
COPY apps/api ./apps/api
37+
COPY infra/docker/api-entrypoint.sh /usr/local/bin/headlessx-api-entrypoint
3738

3839
# Install dependencies
3940
RUN pnpm install --frozen-lockfile
@@ -49,4 +50,5 @@ RUN pnpm exec nx run headlessx-api:build
4950

5051
# Start the API
5152
WORKDIR /app/apps/api
52-
CMD ["pnpm", "start"]
53+
RUN chmod +x /usr/local/bin/headlessx-api-entrypoint
54+
CMD ["/usr/local/bin/headlessx-api-entrypoint"]

infra/docker/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ services:
5252
depends_on:
5353
- api
5454
environment:
55+
- INTERNAL_API_URL=http://api:${PORT:-8000}
5556
- NEXT_PUBLIC_API_URL=http://localhost:${PORT:-8000}
5657
- NODE_ENV=production
5758
- DASHBOARD_INTERNAL_API_KEY=${DASHBOARD_INTERNAL_API_KEY}

0 commit comments

Comments
 (0)