Skip to content

Commit 959ee84

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

36 files changed

Lines changed: 4072 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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 install --frozen-lockfile
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+
COPY --from=builder /app/dist /usr/share/nginx/html
25+
26+
# SPA routing: serve index.html for all unknown paths
27+
RUN echo 'server { \
28+
listen 80; \
29+
root /usr/share/nginx/html; \
30+
index index.html; \
31+
location / { try_files $uri $uri/ /index.html; } \
32+
}' > /etc/nginx/conf.d/default.conf
33+
34+
EXPOSE 80

docker-compose.self-hosted.yml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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:latest
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:latest
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+
mc anonymous set public local/betterbase;
54+
echo 'MinIO bucket initialized.';
55+
"
56+
networks:
57+
- betterbase-internal
58+
59+
# ─── Betterbase Server ─────────────────────────────────────────────────────
60+
betterbase-server:
61+
build:
62+
context: .
63+
dockerfile: packages/server/Dockerfile
64+
container_name: betterbase-server
65+
restart: unless-stopped
66+
depends_on:
67+
postgres:
68+
condition: service_healthy
69+
minio:
70+
condition: service_healthy
71+
environment:
72+
DATABASE_URL: postgresql://betterbase:${POSTGRES_PASSWORD:-betterbase}@postgres:5432/betterbase
73+
BETTERBASE_JWT_SECRET: ${BETTERBASE_JWT_SECRET:?JWT secret required - set BETTERBASE_JWT_SECRET in .env}
74+
BETTERBASE_ADMIN_EMAIL: ${BETTERBASE_ADMIN_EMAIL:-}
75+
BETTERBASE_ADMIN_PASSWORD: ${BETTERBASE_ADMIN_PASSWORD:-}
76+
BETTERBASE_PUBLIC_URL: ${BETTERBASE_PUBLIC_URL:-http://localhost}
77+
STORAGE_ENDPOINT: http://minio:9000
78+
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY:-minioadmin}
79+
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY:-minioadmin}
80+
PORT: "3001"
81+
NODE_ENV: production
82+
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost}
83+
networks:
84+
- betterbase-internal
85+
healthcheck:
86+
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"]
87+
interval: 10s
88+
timeout: 5s
89+
retries: 5
90+
91+
# ─── Dashboard ─────────────────────────────────────────────────────────────
92+
betterbase-dashboard:
93+
build:
94+
context: .
95+
dockerfile: apps/dashboard/Dockerfile # Dashboard Dockerfile — see SH-25
96+
container_name: betterbase-dashboard
97+
restart: unless-stopped
98+
depends_on:
99+
betterbase-server:
100+
condition: service_healthy
101+
environment:
102+
VITE_API_URL: ${BETTERBASE_PUBLIC_URL:-http://localhost}
103+
networks:
104+
- betterbase-internal
105+
106+
# ─── Nginx Reverse Proxy ───────────────────────────────────────────────────
107+
nginx:
108+
image: nginx:alpine
109+
container_name: betterbase-nginx
110+
restart: unless-stopped
111+
depends_on:
112+
- betterbase-server
113+
- betterbase-dashboard
114+
ports:
115+
- "${HTTP_PORT:-80}:80"
116+
volumes:
117+
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
118+
networks:
119+
- betterbase-internal
120+
healthcheck:
121+
test: ["CMD", "nginx", "-t"]
122+
interval: 30s
123+
timeout: 10s
124+
retries: 3
125+
126+
volumes:
127+
postgres_data:
128+
minio_data:
129+
130+
networks:
131+
betterbase-internal:
132+
driver: bridge

0 commit comments

Comments
 (0)