Skip to content

Commit 88b74d8

Browse files
committed
feat: Add self-hosted Betterbase deployment support
1 parent b381dd8 commit 88b74d8

36 files changed

Lines changed: 4099 additions & 135 deletions

BetterBase_SelfHosted_Spec.md

Lines changed: 2288 additions & 0 deletions
Large diffs are not rendered by default.

CODEBASE_MAP.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,40 @@ betterbase/
226226
│ │ ├── constants.ts # Shared constants
227227
│ │ └── utils.ts # Utility functions
228228
│ │
229+
│ ├── server/ # @betterbase/server - Self-hosted server
230+
│ │ ├── package.json
231+
│ │ ├── tsconfig.json
232+
│ │ ├── Dockerfile
233+
│ │ ├── migrations/ # Database migrations
234+
│ │ │ ├── 001_initial_schema.sql
235+
│ │ │ ├── 002_admin_users.sql
236+
│ │ │ ├── 003_projects.sql
237+
│ │ │ └── 004_logs.sql
238+
│ │ └── src/
239+
│ │ ├── index.ts # Server entry point
240+
│ │ ├── lib/
241+
│ │ │ ├── db.ts # Database connection
242+
│ │ │ ├── migrate.ts # Migration runner
243+
│ │ │ ├── env.ts # Environment validation
244+
│ │ │ ├── auth.ts # Auth utilities
245+
│ │ │ └── admin-middleware.ts # Admin auth middleware
246+
│ │ └── routes/
247+
│ │ ├── admin/ # Admin API routes
248+
│ │ │ ├── index.ts
249+
│ │ │ ├── auth.ts
250+
│ │ │ ├── projects.ts
251+
│ │ │ ├── users.ts
252+
│ │ │ ├── metrics.ts
253+
│ │ │ ├── storage.ts
254+
│ │ │ ├── webhooks.ts
255+
│ │ │ ├── functions.ts
256+
│ │ │ └── logs.ts
257+
│ │ └── device/ # Device auth routes
258+
│ │ └── index.ts
259+
│ │
229260
├── apps/
261+
│ ├── dashboard/ # Admin dashboard for self-hosted
262+
│ │ ├── Dockerfile
230263
│ └── test-project/ # Example/test project
231264
│ ├── betterbase.config.ts # Project configuration
232265
│ ├── drizzle.config.ts # Drizzle configuration

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,83 @@ STORAGE_PROVIDER=r2 STORAGE_BUCKET=my-bucket docker-compose -f docker-compose.pr
10151015
- **External database support** - Neon, Supabase, RDS, etc.
10161016
- **S3-compatible storage** - R2, S3, B2, MinIO
10171017

1018+
### Self-Hosted Deployment
1019+
1020+
Betterbase can be self-hosted on your own infrastructure using Docker. This is ideal for teams wanting full control over their data and infrastructure.
1021+
1022+
#### Quick Start
1023+
1024+
```bash
1025+
# Clone the repository
1026+
git clone https://github.com/betterbase/betterbase.git
1027+
cd betterbase
1028+
1029+
# Start self-hosted deployment
1030+
docker-compose -f docker-compose.self-hosted.yml up -d
1031+
```
1032+
1033+
The self-hosted version includes:
1034+
- **Admin Dashboard** - Web UI for managing projects, users, and settings
1035+
- **Device Authentication** - CLI login flow for self-hosted instances
1036+
- **Admin API** - Full API for administrative tasks
1037+
- **Metrics** - Usage and performance tracking
1038+
1039+
#### Configuration
1040+
1041+
Copy the example environment file and configure:
1042+
1043+
```bash
1044+
cp .env.self-hosted.example .env
1045+
```
1046+
1047+
Key environment variables:
1048+
1049+
| Variable | Description | Required |
1050+
|----------|-------------|----------|
1051+
| `DATABASE_URL` | PostgreSQL connection string | Yes |
1052+
| `AUTH_SECRET` | Secret for auth tokens (min 32 chars) | Yes |
1053+
| `SERVER_URL` | Public URL of your instance | Yes |
1054+
| `ADMIN_EMAIL` | Initial admin email | Yes |
1055+
| `ADMIN_PASSWORD` | Initial admin password | Yes |
1056+
| `STORAGE_PROVIDER` | Storage provider (local, s3, r2, backblaze, minio) | No |
1057+
| `STORAGE_BUCKET` | Storage bucket name | No |
1058+
1059+
#### CLI Login with Self-Hosted
1060+
1061+
```bash
1062+
# Login to your self-hosted instance
1063+
bb login --url https://your-instance.com
1064+
1065+
# This will initiate device authentication flow
1066+
# 1. You'll be given a device code
1067+
# 2. Open the admin dashboard
1068+
# 3. Approve the device
1069+
# 4. CLI will receive credentials automatically
1070+
```
1071+
1072+
#### Docker Compose Services
1073+
1074+
| Service | Port | Description |
1075+
|---------|------|-------------|
1076+
| server | 3000 | Main API server |
1077+
| dashboard | 3001 | Admin dashboard |
1078+
| nginx | 80, 443 | Reverse proxy |
1079+
1080+
#### For Development
1081+
1082+
```bash
1083+
# Start all services
1084+
docker-compose -f docker-compose.self-hosted.yml up
1085+
1086+
# View logs
1087+
docker-compose -f docker-compose.self-hosted.yml logs -f
1088+
1089+
# Stop services
1090+
docker-compose -f docker-compose.self-hosted.yml down
1091+
```
1092+
1093+
See [SELF_HOSTED.md](SELF_HOSTED.md) for detailed documentation.
1094+
10181095
### Cloud Providers
10191096

10201097
| Provider | Deployment Method |

SELF_HOSTED.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Self-Hosting Betterbase
2+
3+
## Prerequisites
4+
5+
- Docker and Docker Compose
6+
- Ports 80 (or your chosen `HTTP_PORT`) available
7+
8+
## Quick Start
9+
10+
**1. Copy the example env file:**
11+
```bash
12+
cp .env.self-hosted.example .env
13+
```
14+
15+
**2. Edit `.env` — at minimum set these two values:**
16+
```bash
17+
BETTERBASE_JWT_SECRET=your-random-string-here # min 32 chars
18+
BETTERBASE_ADMIN_EMAIL=you@example.com
19+
BETTERBASE_ADMIN_PASSWORD=yourpassword
20+
```
21+
Generate a secret: `openssl rand -base64 32`
22+
23+
**3. Start everything:**
24+
```bash
25+
docker compose -f docker-compose.self-hosted.yml up -d
26+
```
27+
28+
**4. Open the dashboard:**
29+
Navigate to `http://localhost` (or your configured `BETTERBASE_PUBLIC_URL`).
30+
31+
**5. Connect your CLI:**
32+
```bash
33+
bb login --url http://localhost
34+
```
35+
36+
---
37+
38+
## What Runs
39+
40+
| Service | Internal Port | Description |
41+
|---------|--------------|-------------|
42+
| nginx | 80 (public) | Reverse proxy — only public-facing port |
43+
| betterbase-server | 3001 (internal) | API server |
44+
| betterbase-dashboard | 80 (internal) | Dashboard UI |
45+
| postgres | 5432 (internal) | Betterbase metadata database |
46+
| minio | 9000 (internal) | S3-compatible object storage |
47+
48+
---
49+
50+
## CLI Usage Against Self-Hosted
51+
52+
After `bb login --url http://your-server`, all CLI commands automatically target your server.
53+
54+
```bash
55+
bb login --url http://localhost # authenticate
56+
bb init my-project # create a project (registered to your local instance)
57+
bb sync # sync local project to server
58+
```
59+
60+
---
61+
62+
## Production Checklist
63+
64+
- [ ] `BETTERBASE_JWT_SECRET` is a random 32+ character string
65+
- [ ] `POSTGRES_PASSWORD` changed from default
66+
- [ ] `STORAGE_ACCESS_KEY` and `STORAGE_SECRET_KEY` changed from defaults
67+
- [ ] `BETTERBASE_PUBLIC_URL` set to your actual domain
68+
- [ ] SSL/TLS termination configured (add HTTPS to the nginx config or use a load balancer)
69+
- [ ] Remove `BETTERBASE_ADMIN_EMAIL` / `BETTERBASE_ADMIN_PASSWORD` from `.env` after first start (or keep — seeding is idempotent)
70+
71+
---
72+
73+
## Troubleshooting
74+
75+
**Server won't start:**
76+
Check that `BETTERBASE_JWT_SECRET` is set (minimum 32 characters). Run:
77+
```bash
78+
docker compose -f docker-compose.self-hosted.yml logs betterbase-server
79+
```
80+
81+
**Can't log in with CLI:**
82+
Ensure `BETTERBASE_PUBLIC_URL` in your `.env` matches the URL you pass to `bb login --url`.
83+
84+
**Storage not working:**
85+
The `minio-init` container initialises the default bucket on first start. Check its logs:
86+
```bash
87+
docker compose -f docker-compose.self-hosted.yml logs minio-init
88+
```

apps/dashboard/Dockerfile

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Dashboard Dockerfile placeholder
2+
# This is a placeholder. The actual dashboard needs to be created first.
3+
# If the dashboard uses Bun instead of npm, replace node:20-alpine with oven/bun:1.2-alpine
4+
# and replace npm install with bun install and npm run build with bun run build
5+
6+
FROM node:20-alpine AS builder
7+
8+
WORKDIR /app
9+
10+
COPY apps/dashboard/package.json apps/dashboard/package-lock.json* ./
11+
RUN npm ci
12+
13+
COPY apps/dashboard ./
14+
15+
# Inject API URL at build time
16+
ARG VITE_API_URL=http://localhost
17+
ENV VITE_API_URL=$VITE_API_URL
18+
19+
RUN npm run build
20+
21+
# --- Runtime: serve static files with nginx ---
22+
FROM nginx:alpine
23+
24+
# Create non-root user for nginx
25+
RUN addgroup -g 1000 -S appgroup && \
26+
adduser -u 1000 -S appuser -G appgroup
27+
28+
COPY --from=builder /app/dist /usr/share/nginx/html
29+
30+
# Ensure non-root user can read the html directory
31+
RUN chown -R appuser:appgroup /usr/share/nginx/html && \
32+
chown -R appuser:appgroup /etc/nginx/conf.d
33+
34+
# SPA routing: serve index.html for all unknown paths
35+
RUN echo 'server { \
36+
listen 80; \
37+
root /usr/share/nginx/html; \
38+
index index.html; \
39+
location / { try_files $uri $uri/ /index.html; } \
40+
}' > /etc/nginx/conf.d/default.conf
41+
42+
# Switch to non-root user
43+
USER appuser
44+
45+
EXPOSE 80

docker-compose.self-hosted.yml

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
version: "3.9"
2+
3+
services:
4+
# ─── Postgres ──────────────────────────────────────────────────────────────
5+
postgres:
6+
image: postgres:16-alpine
7+
container_name: betterbase-postgres
8+
restart: unless-stopped
9+
environment:
10+
POSTGRES_USER: betterbase
11+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-betterbase}
12+
POSTGRES_DB: betterbase
13+
volumes:
14+
- postgres_data:/var/lib/postgresql/data
15+
healthcheck:
16+
test: ["CMD-SHELL", "pg_isready -U betterbase"]
17+
interval: 10s
18+
timeout: 5s
19+
retries: 5
20+
networks:
21+
- betterbase-internal
22+
23+
# ─── MinIO (S3-compatible storage) ─────────────────────────────────────────
24+
minio:
25+
image: minio/minio:RELEASE.2024-01-16T16-07-38Z
26+
container_name: betterbase-minio
27+
restart: unless-stopped
28+
command: server /data --console-address ":9001"
29+
environment:
30+
MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY:-minioadmin}
31+
MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_KEY:-minioadmin}
32+
volumes:
33+
- minio_data:/data
34+
healthcheck:
35+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
36+
interval: 10s
37+
timeout: 5s
38+
retries: 5
39+
networks:
40+
- betterbase-internal
41+
42+
# ─── MinIO bucket init (runs once, exits) ──────────────────────────────────
43+
minio-init:
44+
image: minio/mc:RELEASE.2024-01-06T18-51-57Z
45+
container_name: betterbase-minio-init
46+
depends_on:
47+
minio:
48+
condition: service_healthy
49+
entrypoint: >
50+
/bin/sh -c "
51+
mc alias set local http://minio:9000 ${STORAGE_ACCESS_KEY:-minioadmin} ${STORAGE_SECRET_KEY:-minioadmin};
52+
mc mb --ignore-existing local/betterbase;
53+
echo 'MinIO bucket initialized.';
54+
"
55+
networks:
56+
- betterbase-internal
57+
58+
# ─── Betterbase Server ─────────────────────────────────────────────────────
59+
betterbase-server:
60+
build:
61+
context: .
62+
dockerfile: packages/server/Dockerfile
63+
container_name: betterbase-server
64+
restart: unless-stopped
65+
depends_on:
66+
postgres:
67+
condition: service_healthy
68+
minio:
69+
condition: service_healthy
70+
minio-init:
71+
condition: service_completed_successfully
72+
environment:
73+
DATABASE_URL: postgresql://betterbase:${POSTGRES_PASSWORD:-betterbase}@postgres:5432/betterbase
74+
BETTERBASE_JWT_SECRET: ${BETTERBASE_JWT_SECRET:?JWT secret required - set BETTERBASE_JWT_SECRET in .env}
75+
BETTERBASE_ADMIN_EMAIL: ${BETTERBASE_ADMIN_EMAIL:-}
76+
BETTERBASE_ADMIN_PASSWORD: ${BETTERBASE_ADMIN_PASSWORD:-}
77+
BETTERBASE_PUBLIC_URL: ${BETTERBASE_PUBLIC_URL:-http://localhost}
78+
STORAGE_ENDPOINT: http://minio:9000
79+
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY:-minioadmin}
80+
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY:-minioadmin}
81+
PORT: "3001"
82+
NODE_ENV: production
83+
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost}
84+
networks:
85+
- betterbase-internal
86+
healthcheck:
87+
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"]
88+
interval: 10s
89+
timeout: 5s
90+
retries: 5
91+
92+
# ─── Dashboard ─────────────────────────────────────────────────────────────
93+
betterbase-dashboard:
94+
build:
95+
context: .
96+
dockerfile: apps/dashboard/Dockerfile # Dashboard Dockerfile — see SH-25
97+
args:
98+
VITE_API_URL: ${BETTERBASE_PUBLIC_URL:-http://localhost}
99+
container_name: betterbase-dashboard
100+
restart: unless-stopped
101+
depends_on:
102+
betterbase-server:
103+
condition: service_healthy
104+
networks:
105+
- betterbase-internal
106+
107+
# ─── Nginx Reverse Proxy ───────────────────────────────────────────────────
108+
nginx:
109+
image: nginx:alpine
110+
container_name: betterbase-nginx
111+
restart: unless-stopped
112+
depends_on:
113+
- betterbase-server
114+
- betterbase-dashboard
115+
ports:
116+
- "${HTTP_PORT:-80}:80"
117+
volumes:
118+
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
119+
networks:
120+
- betterbase-internal
121+
healthcheck:
122+
test: ["CMD", "nginx", "-t"]
123+
interval: 30s
124+
timeout: 10s
125+
retries: 3
126+
127+
volumes:
128+
postgres_data:
129+
minio_data:
130+
131+
networks:
132+
betterbase-internal:
133+
driver: bridge

0 commit comments

Comments
 (0)