Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 183 additions & 50 deletions content/guides/self-hosting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,133 @@ When you self-host the Happy Server, you get:
- **Team control** - Run one server for your whole team
- **Zero dependencies** - Never worry about a service shutting down

The entire server is only 1,293 lines of typescript. You can read it yourself to
verify it just forwards encrypted messages.
The server is fully open source and part of the [Happy monorepo](https://github.com/slopus/happy).
You can read the code yourself to verify it just forwards encrypted messages.

## Quick Start
## Quick Start (Easiest)

The simplest way to self-host is with the `happy` CLI. No Docker, no database setup needed.

<Steps>
### Install Happy

```bash
npm install -g happy
```

### Start the Server

```bash
happy server
```

On first start, this will:
- Create a master secret in `~/.happy/server/master-secret`
- Start an embedded database (PGlite) - no Postgres needed
- Serve on `http://127.0.0.1:3005`
- Write the server URL to your local Happy settings

To expose the server on your local network (for mobile access):

```bash
happy server --host 0.0.0.0 --port 3005
```

### Configure Your Devices

**On your phone:**
1. Open Happy app
2. Go to Settings
3. Set "Relay Server URL" to `http://your-server:3005`
4. Save

**On your computer:**
```bash
export HAPPY_SERVER_URL="http://your-server:3005"
```

If you used `happy server`, the CLI automatically picks up the local server URL from your settings.

</Steps>

That's it! Your Happy setup now runs through your own server.

## Quick Start with Docker

The standalone Docker image runs everything in a single container with no external dependencies (no Postgres, no Redis, no S3).

<Steps>
### Clone and Build

```bash
# Get the code
git clone https://github.com/slopus/happy-server
cd happy-server
git clone https://github.com/slopus/happy
cd happy

# Build with Docker
docker build -t happy-server:latest .
docker build -t happy-server -f Dockerfile.server .
```

### Run the Server

```bash
docker run -d \
--name happy-server \
-p 3000:3000 \
-e NODE_ENV=production \
-e DATABASE_URL="postgresql://postgres:postgres@localhost:5432/happy-server" \
-e REDIS_URL="redis://localhost:6379" \
-e SEED="your-seed-for-token-generation" \
-e PORT=3005 \
-p 3005:3005 \
-e HANDY_MASTER_SECRET="$(openssl rand -hex 32)" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select PGlite before advertising standalone Docker

For the Docker examples that claim to use embedded PGlite, this env block never selects the PGlite provider. In the current Happy server code, packages/happy-server/sources/storage/db.ts defaults DB_PROVIDER to postgres, and the CLI path is what sets DB_PROVIDER=pglite and runs migrations before serve; with this documented command there is also no DATABASE_URL, so the container exits at startup instead of running the one-container setup. Please add the PGlite provider/migration path to the Docker examples, or document that this image requires an external database.

Useful? React with 👍 / 👎.

-v happy-data:/data \
--restart unless-stopped \
happy-server:latest
happy-server
```

**Important**: Replace the environment variable values with your actual configuration:
- `DATABASE_URL`: Your PostgreSQL connection string
- `REDIS_URL`: Your Redis connection string
- `SEED`: A secure random seed for token generation
- `PORT`: The port the application listens on (3005)
This uses:
- **PGlite** - an embedded PostgreSQL database (data stored in `/data/pglite`)
- **Local filesystem** - for file uploads (stored in `/data/files`)
- **In-memory event bus** - no Redis needed

Data persists in the `happy-data` Docker volume across container restarts.

### Configure Your Devices

**On your phone:**
1. Open Happy app
2. Go to Settings
3. Set "Relay Server URL" to `http://your-server:3000`
3. Set "Relay Server URL" to `http://your-server:3005`
4. Save

**On your computer:**
```bash
export HAPPY_SERVER_URL="http://your-server:3000"
# Configure your CLI to use your server
export HAPPY_SERVER_URL="http://your-server:3005"
```

</Steps>

That's it! Your Happy setup now runs through your own server.
### Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `HANDY_MASTER_SECRET` | Yes | - | Master secret for auth and encryption |
| `PORT` | No | `3005` | Server port |
| `PUBLIC_URL` | No | `http://localhost:3005` | Public base URL for file URLs sent to clients |
| `DATA_DIR` | No | `/data` | Base data directory |
| `PGLITE_DIR` | No | `/data/pglite` | PGlite database directory |
Comment on lines +123 to +124

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't claim Docker data defaults to /data

These defaults do not match the server defaults: the Happy server uses ./data and ./data/pglite unless DATA_DIR/PGLITE_DIR are set. Because the Docker command only mounts happy-data:/data and does not set those env vars, users who recreate the container after following the guide will not have their database/uploads in the named volume despite the later persistence claim. Please either set DATA_DIR=/data and PGLITE_DIR=/data/pglite in the Docker examples or correct the default paths.

Useful? React with 👍 / 👎.

| `NODE_ENV` | No | `production` | Environment mode |

### Optional: External Services

For larger deployments, you can use external Postgres or Redis instead of the embedded defaults:

| Variable | Description |
|----------|-------------|
| `DATABASE_URL` | PostgreSQL connection URL (bypasses PGlite) |
| `REDIS_URL` | Redis connection URL (enables cross-process pub/sub) |
| `S3_HOST` | S3/MinIO host (bypasses local file storage) |
| `S3_PORT` | S3 port |
| `S3_ACCESS_KEY` | S3 access key |
| `S3_SECRET_KEY` | S3 secret key |
| `S3_BUCKET` | S3 bucket name |
| `S3_PUBLIC_URL` | Public URL for S3 assets |
| `S3_USE_SSL` | Whether to use SSL for S3 (`true`/`false`, default `true`) |

## Production Setup with HTTPS

Expand All @@ -83,7 +155,7 @@ sudo apt install caddy
# Configure reverse proxy
sudo tee /etc/caddy/Caddyfile <<EOF
your-domain.com {
reverse_proxy localhost:3000
reverse_proxy localhost:3005
}
EOF

Expand All @@ -95,31 +167,55 @@ Now use `https://your-domain.com` in your Happy Coder settings.

## Docker Compose Setup

For easier management, use Docker Compose:
For standalone mode (recommended for most users), you only need the Happy Server container:

```yaml
# docker-compose.yml
services:
happy-server:
build:
context: .
dockerfile: Dockerfile.server
ports:
- "3005:3005"
restart: unless-stopped
environment:
- HANDY_MASTER_SECRET=replace-with-a-strong-random-secret
volumes:
- happy-data:/data

volumes:
happy-data:
```

Run with: `docker compose up -d`

### Docker Compose with External Postgres and Redis

For larger teams or production deployments, you can use external Postgres and Redis:

```yaml
# docker-compose.yml
version: '3.8'
services:
happy-server:
image: happy-server:latest
build:
context: .
dockerfile: Dockerfile.server
ports:
- "3000:3000"
- "3005:3005"
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/happy-server
- REDIS_URL=redis://localhost:6379
- SEED=your-seed-for-token-generation
- PORT=3005
- HANDY_MASTER_SECRET=replace-with-a-strong-random-secret
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/happy
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis

postgres:
image: postgres:15
environment:
- POSTGRES_DB=happy-server
- POSTGRES_DB=happy
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
Expand All @@ -139,10 +235,6 @@ volumes:
redis_data:
```

Run with: `docker-compose up -d`

This setup includes PostgreSQL and Redis containers that the Happy Server depends on.

## Where to Host

### Option 1: Home Server (Free)
Expand Down Expand Up @@ -185,12 +277,14 @@ Check server health:
docker logs -f happy-server

# Check health endpoint
curl http://your-server:3000/health
curl http://your-server:3005/health

# See connection count
curl http://your-server:3000/stats
# See connection count
curl http://your-server:3005/stats
```

If you enabled metrics (`METRICS_ENABLED=true`), Prometheus metrics are available on port 9090 by default (`METRICS_PORT`).

## Backup Your Data

The server stores encrypted blobs in `/data`. To backup:
Expand All @@ -205,6 +299,49 @@ rsync -av ./data/ backup-location/

Remember: These backups are encrypted. No one can read them without your device keys.

## S3 Storage Configuration

When using S3 for file storage (set `S3_HOST`), image attachments and other blobs are stored under
`sessions/<sessionId>/attachments/<id>.enc`. Two bucket-level settings should be applied once at deploy time:

**1. Lifecycle rule for attachment TTL.** Add a lifecycle rule so objects age out automatically. Pick a TTL that matches your retention policy (30 days is a reasonable default).

```bash
# AWS CLI
aws s3api put-bucket-lifecycle-configuration --bucket happy-blobs \
--lifecycle-configuration '{
"Rules": [{
"ID": "session-attachments-ttl",
"Status": "Enabled",
"Filter": { "Prefix": "sessions/" },
"Expiration": { "Days": 30 }
}]
}'

# MinIO
mc ilm rule add myminio/happy-blobs \
--expire-days 30 \
--prefix "sessions/"
```

**2. Server-side encryption (defense-in-depth).** Blobs are already end-to-end encrypted by the client, but enabling AES-256 SSE on the bucket adds an extra layer of protection.

```bash
# AWS CLI
aws s3api put-bucket-encryption --bucket happy-blobs \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}]
}'

# MinIO
mc encrypt set sse-s3 myminio/happy-blobs
```

When using local storage mode (no `S3_HOST`), blobs are written under `<DATA_DIR>/files/sessions/<sessionId>/attachments/`. Clean up old session directories on a cron if you want a TTL.

## Security Notes

Expand Down Expand Up @@ -291,11 +428,6 @@ spec:
type: ClusterIP
```

**Configuration Notes**:
- All ports now consistently use 3005 (the application's actual listening port)
- Uses the `happy-secrets` secret created in the previous section
- Update the image name to match your actual container registry

## Kubernetes Secrets

Create a secret file for your environment variables:
Expand All @@ -308,11 +440,12 @@ metadata:
name: happy-secrets
type: Opaque
stringData:
DATABASE_URL: "postgresql://postgres:postgres@postgres-service:5432/happy-server"
REDIS_URL: "redis://redis-service:6379"
SEED: "your-secure-seed-for-token-generation"
HANDY_MASTER_SECRET: "replace-with-a-strong-random-secret"
PORT: "3005"
NODE_ENV: "production"
# Add these if using external Postgres/Redis:
# DATABASE_URL: "postgresql://postgres:postgres@postgres-service:5432/happy"
# REDIS_URL: "redis://redis-service:6379"
```

Apply the secret:
Expand All @@ -321,7 +454,7 @@ kubectl apply -f happy-secrets.yaml
```

**Security Note**: Replace the values with your actual configuration. For production:
- Use a strong, random SEED value
- Use a strong, random `HANDY_MASTER_SECRET` value (e.g., `openssl rand -hex 32`)
- Use proper database credentials
- Consider using external secret management tools like external-secrets-operator with Vault

Expand All @@ -333,4 +466,4 @@ After setting up your server:
2. [Set up monitoring](/guides/monitoring)
3. [Enable voice coding](/docs/features/voice-coding-with-claude-code)

Running your own Happy Server takes 3 minutes and gives you permanent control over your Claude Code mobile access. No subscriptions, no lock-in, no surprises.
Running your own Happy Server takes 3 minutes and gives you permanent control over your Claude Code mobile access. No subscriptions, no lock-in, no surprises.