Skip to content

Commit fa67291

Browse files
committed
feat: Docker, security hardening, update system, tests & cleanup
Docker: Multi-stage Dockerfile (1.51GB), docker-compose with prod/dev/auto-update, GHCR CI/CD, Watchtower Security: withSecurity on all 35 routes, git proxy allowlist, CORS fix, SameSite cookies Updates: scripts/update.cjs, docker:update, /api/version-check, UI banner Tests: 6 new test files, 282 tests passing Cleanup: zustand->nanostores, 13 unused pkgs removed, icon fixes, perf optimizations, .env.example updated
1 parent fae8000 commit fa67291

66 files changed

Lines changed: 2323 additions & 449 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# ─────────────────────────────────────────────────────────────
2+
# .dockerignore — Devonz (bolt.diy)
3+
# Keep Docker context small and fast
4+
# ─────────────────────────────────────────────────────────────
5+
6+
# Dependencies (installed in container)
7+
node_modules
8+
9+
# Build output (rebuilt in container)
10+
build
11+
dist
12+
dist-ssr
13+
14+
# Git
15+
.git
16+
.gitignore
17+
18+
# IDE / Editor
19+
.vscode
20+
.idea
21+
*.sw?
22+
23+
# Environment files (passed via env_file / compose)
24+
.env
25+
.env.local
26+
.env.production
27+
.dev.vars
28+
*.vars
29+
30+
# Docker files themselves
31+
Dockerfile
32+
docker-compose.yml
33+
.dockerignore
34+
35+
# Logs
36+
logs
37+
*.log
38+
npm-debug.log*
39+
pnpm-debug.log*
40+
41+
# OS files
42+
.DS_Store
43+
Thumbs.db
44+
45+
# CI / misc
46+
.github
47+
.husky
48+
.wrangler
49+
.cache
50+
.history
51+
52+
# Docs and plans (not needed at runtime)
53+
docs
54+
plan
55+
56+
# Test files
57+
**/*.spec.ts
58+
**/*.test.ts
59+
vitest.config.*
60+
61+
# Supabase local config
62+
supabase

.env.example

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,33 @@ VITE_LOG_LEVEL=debug
198198
# Default Context Window Size (for local models)
199199
DEFAULT_NUM_CTX=32768
200200

201+
# Disable IndexedDB chat persistence (set to "true" to disable)
202+
# VITE_DISABLE_PERSISTENCE=
203+
204+
# ======================================
205+
# DOCKER SETTINGS
206+
# ======================================
207+
208+
# Set to "true" when running inside Docker
209+
# Automatically switches Ollama/LMStudio URLs to host.docker.internal
210+
RUNNING_IN_DOCKER=false
211+
212+
# ======================================
213+
# CUSTOM MODEL LIST (OpenAI-Like Provider)
214+
# ======================================
215+
216+
# Comma-separated list of model names for the OpenAI-Like provider
217+
# OPENAI_LIKE_API_MODELS=model-1,model-2
218+
219+
# ======================================
220+
# BUG REPORTING (Optional)
221+
# ======================================
222+
223+
# GitHub repo for automatic bug reports (format: owner/repo)
224+
# BUG_REPORT_REPO=
225+
# GitHub token with issues:write scope for bug reports
226+
# GITHUB_BUG_REPORT_TOKEN=
227+
201228
# ======================================
202229
# SETUP INSTRUCTIONS
203230
# ======================================
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# ─────────────────────────────────────────────────────────────
2+
# Build & push Docker image to GitHub Container Registry
3+
# Triggers on every push to main and on tags (v*)
4+
# ─────────────────────────────────────────────────────────────
5+
6+
name: Docker Build & Push
7+
8+
on:
9+
push:
10+
branches: [main]
11+
tags: ['v*']
12+
workflow_dispatch: # manual trigger
13+
14+
env:
15+
REGISTRY: ghcr.io
16+
IMAGE_NAME: ${{ github.repository }}
17+
18+
permissions:
19+
contents: read
20+
packages: write
21+
22+
jobs:
23+
build-and-push:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Set up Docker Buildx
30+
uses: docker/setup-buildx-action@v3
31+
32+
- name: Log in to GHCR
33+
uses: docker/login-action@v3
34+
with:
35+
registry: ${{ env.REGISTRY }}
36+
username: ${{ github.actor }}
37+
password: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Extract metadata (tags, labels)
40+
id: meta
41+
uses: docker/metadata-action@v5
42+
with:
43+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
44+
tags: |
45+
# latest on every push to main
46+
type=raw,value=latest,enable={{is_default_branch}}
47+
# sha-abc1234 on every push
48+
type=sha,prefix=sha-
49+
# v1.0.0 on tags
50+
type=semver,pattern={{version}}
51+
type=semver,pattern={{major}}.{{minor}}
52+
53+
- name: Build and push
54+
uses: docker/build-push-action@v6
55+
with:
56+
context: .
57+
push: true
58+
tags: ${{ steps.meta.outputs.tags }}
59+
labels: ${{ steps.meta.outputs.labels }}
60+
cache-from: type=gha
61+
cache-to: type=gha,mode=max
62+
platforms: linux/amd64

Dockerfile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# ─────────────────────────────────────────────────────────────
2+
# Devonz (bolt.diy) — Multi-stage Docker build
3+
# Optimised for pnpm + Remix on Node 20 LTS
4+
# ─────────────────────────────────────────────────────────────
5+
6+
# ── Stage 1: base ─────────────────────────────────────────────
7+
# Shared base with corepack-managed pnpm
8+
FROM node:20-slim AS base
9+
ENV PNPM_HOME="/pnpm"
10+
ENV PATH="$PNPM_HOME:$PATH"
11+
RUN corepack enable && corepack prepare pnpm@9.14.4 --activate
12+
WORKDIR /app
13+
14+
# ── Stage 2: deps ─────────────────────────────────────────────
15+
# Install ALL dependencies (dev + prod) for the build step
16+
FROM base AS deps
17+
COPY package.json pnpm-lock.yaml ./
18+
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
19+
pnpm install --frozen-lockfile
20+
21+
# ── Stage 3: build ────────────────────────────────────────────
22+
# Build the Remix application
23+
FROM deps AS build
24+
COPY . .
25+
26+
# Git info for pre-start.cjs (falls back to 'no-git-info' in Docker)
27+
RUN node pre-start.cjs
28+
RUN pnpm build
29+
30+
# ── Stage 4: prod-deps ───────────────────────────────────────
31+
# Prune to production deps only (remix-serve is now a prod dep)
32+
FROM build AS prod-deps
33+
RUN pnpm prune --prod --ignore-scripts
34+
35+
# ── Stage 5: runtime ─────────────────────────────────────────
36+
# Minimal final image — only build output + prod deps
37+
FROM node:20-slim AS runtime
38+
ENV NODE_ENV="production"
39+
ENV PORT="5173"
40+
WORKDIR /app
41+
42+
# git: needed by api.git-info.ts (execSync('git ...'))
43+
# curl: needed for healthchecks on some container platforms
44+
RUN apt-get update && apt-get install -y --no-install-recommends git curl \
45+
&& rm -rf /var/lib/apt/lists/*
46+
47+
# Copy node_modules (includes remix-serve needed for runtime)
48+
COPY --from=prod-deps /app/node_modules ./node_modules
49+
50+
# Copy build output
51+
COPY --from=build /app/build ./build
52+
53+
# Copy package.json (needed by remix-serve)
54+
COPY --from=build /app/package.json ./
55+
56+
# Non-root user for security
57+
RUN groupadd --system --gid 1001 appgroup && \
58+
useradd --system --uid 1001 --gid appgroup --create-home appuser && \
59+
chown -R appuser:appgroup /app
60+
USER appuser
61+
62+
EXPOSE 5173
63+
CMD ["node", "node_modules/@remix-run/serve/dist/cli.js", "./build/server/index.js"]

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ Devonz is an AI-powered development agent that helps you build full-stack applic
111111

112112
4. **Open in Browser**: Navigate to `http://localhost:5173`
113113

114+
### Docker
115+
116+
Run Devonz in a Docker container without installing Node.js locally:
117+
118+
```bash
119+
# Build the image
120+
pnpm docker:build
121+
# or: docker build -t devonz .
122+
123+
# Run with your API keys
124+
pnpm docker:run
125+
# or: docker run --rm -p 5173:5173 --env-file .env.local devonz
126+
127+
# Using Docker Compose
128+
pnpm docker:up # start in background
129+
pnpm docker:down # stop
130+
131+
# Development mode (hot reload)
132+
pnpm docker:dev
133+
```
134+
135+
Open `http://localhost:5173` after the container starts.
136+
114137
---
115138

116139
## Configuration
@@ -241,6 +264,39 @@ bolt.diy/
241264
| `pnpm run clean` | Clean build artifacts |
242265
| `pnpm run prepare` | Set up husky git hooks |
243266

267+
### Docker Scripts
268+
269+
| Command | Description |
270+
| --------------------- | ------------------------------------------ |
271+
| `pnpm docker:build` | Build production Docker image locally |
272+
| `pnpm docker:run` | Run container (standalone) |
273+
| `pnpm docker:up` | Start via Docker Compose (pulls from GHCR) |
274+
| `pnpm docker:down` | Stop Docker Compose services |
275+
| `pnpm docker:dev` | Dev mode with hot reload in Docker |
276+
| `pnpm docker:update` | Pull latest image + restart |
277+
278+
### Keeping Up to Date
279+
280+
**Git Clone users:**
281+
282+
```bash
283+
pnpm run update # pulls latest, installs deps, rebuilds
284+
pnpm run update -- --skip-build # pull + install only
285+
```
286+
287+
**Docker users:**
288+
289+
```bash
290+
pnpm docker:update # pulls latest image, restarts container
291+
```
292+
293+
**Docker auto-update (hands-free):**
294+
295+
```bash
296+
# Enable Watchtower — auto-pulls new images every 5 minutes
297+
docker compose --profile auto-update up -d
298+
```
299+
244300
---
245301

246302
## Settings and Features

app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function GitHubRepositoryCard({ repo, onClone }: GitHubRepositoryCardProp
1919
<div className="flex-1 space-y-3">
2020
<div className="flex items-start justify-between">
2121
<div className="flex items-center gap-2">
22-
<div className="i-ph:git-repository w-4 h-4 text-bolt-elements-icon-info" />
22+
<div className="i-ph:git-branch w-4 h-4 text-bolt-elements-icon-info" />
2323
<h5 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-bolt-elements-item-contentAccent transition-colors">
2424
{repo.name}
2525
</h5>

app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function RepositoryCard({ repo, onClone }: RepositoryCardProps) {
1818
<div className="space-y-3">
1919
<div className="flex items-start justify-between">
2020
<div className="flex items-center gap-2">
21-
<div className="i-ph:git-repository w-4 h-4 text-bolt-elements-icon-info" />
21+
<div className="i-ph:git-branch w-4 h-4 text-bolt-elements-icon-info" />
2222
<h5 className="text-sm font-medium text-bolt-elements-textPrimary group-hover:text-bolt-elements-item-contentAccent transition-colors">
2323
{repo.name}
2424
</h5>

app/components/@settings/tabs/mcp/McpTab.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useEffect, useMemo, useState } from 'react';
2+
import { useStore } from '@nanostores/react';
23
import { classNames } from '~/utils/classNames';
34
import type { MCPConfig } from '~/lib/services/mcpService';
45
import { toast } from 'react-toastify';
5-
import { useMCPStore } from '~/lib/stores/mcp';
6+
import { mcpStore, initializeMCP, updateMCPSettings, checkMCPServersAvailabilities } from '~/lib/stores/mcp';
67
import McpServerList from '~/components/@settings/tabs/mcp/McpServerList';
78

89
const EXAMPLE_MCP_CONFIG: MCPConfig = {
@@ -27,12 +28,7 @@ const EXAMPLE_MCP_CONFIG: MCPConfig = {
2728
};
2829

2930
export default function McpTab() {
30-
const settings = useMCPStore((state) => state.settings);
31-
const isInitialized = useMCPStore((state) => state.isInitialized);
32-
const serverTools = useMCPStore((state) => state.serverTools);
33-
const initialize = useMCPStore((state) => state.initialize);
34-
const updateSettings = useMCPStore((state) => state.updateSettings);
35-
const checkServersAvailabilities = useMCPStore((state) => state.checkServersAvailabilities);
31+
const { settings, isInitialized, serverTools } = useStore(mcpStore);
3632

3733
const [isSaving, setIsSaving] = useState(false);
3834
const [mcpConfigText, setMCPConfigText] = useState('');
@@ -43,7 +39,7 @@ export default function McpTab() {
4339

4440
useEffect(() => {
4541
if (!isInitialized) {
46-
initialize().catch((err) => {
42+
initializeMCP().catch((err) => {
4743
setError(`Failed to initialize MCP settings: ${err instanceof Error ? err.message : String(err)}`);
4844
toast.error('Failed to load MCP configuration');
4945
});
@@ -78,7 +74,7 @@ export default function McpTab() {
7874
setIsSaving(true);
7975

8076
try {
81-
await updateSettings({
77+
await updateMCPSettings({
8278
mcpConfig: parsedConfig,
8379
maxLLMSteps,
8480
});
@@ -107,7 +103,7 @@ export default function McpTab() {
107103
setError(null);
108104

109105
try {
110-
await checkServersAvailabilities();
106+
await checkMCPServersAvailabilities();
111107
} catch (e) {
112108
setError(`Failed to check server availability: ${e instanceof Error ? e.message : String(e)}`);
113109
} finally {

app/components/@settings/tabs/netlify/NetlifyTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ export default function NetlifyTab() {
11311131
disabled={isActionLoading}
11321132
className="flex items-center gap-1 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary"
11331133
>
1134-
<div className="i-ph:lock-closed w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
1134+
<div className="i-ph:lock w-4 h-4 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
11351135
Lock
11361136
</Button>
11371137
) : (

app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const PROVIDER_ICONS: Record<ProviderName, string> = {
3939
HuggingFace: 'i-ph:robot',
4040
Hyperbolic: 'i-ph:cloud',
4141
Mistral: 'i-ph:brain',
42-
OpenAI: 'i-ph:openai-logo',
42+
OpenAI: 'i-ph:brain',
4343
OpenRouter: 'i-ph:cloud',
4444
Perplexity: 'i-ph:sparkle',
4545
Together: 'i-ph:cloud',

0 commit comments

Comments
 (0)