Skip to content
Merged
Show file tree
Hide file tree
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
84 changes: 84 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Dependencies
node_modules
bun.lock
package-lock.json
yarn.lock

# Next.js build output
.next
out

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# IDE
.vscode
.idea
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Git
.git
.gitignore

# Docker
Dockerfile*
docker-compose*
.dockerignore

# Documentation
README.md
CLAUDE.md
LICENSE

# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# ESLint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# parcel-bundler cache
.cache
.parcel-cache

# Temporary folders
tmp/
temp/


# Development scripts
scripts/dev*
53 changes: 40 additions & 13 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
# Database Configuration
DATABASE_URL=postgresql://username:password@localhost:5432/command_center
# ===========================================
# REQUIRED FOR DOCKER DEPLOYMENT
# ===========================================

# Your public URL (e.g., https://commandops.yourdomain.com or http://localhost:3000)
NEXT_PUBLIC_BASE_URL=http://localhost:3000

# Authentication secret - Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=your-super-secure-secret-key-min-32-chars

# Database password for PostgreSQL (used by Docker Compose)
POSTGRES_PASSWORD=your-secure-db-password

# Redis password (used by Docker Compose)
REDIS_PASSWORD=your-secure-redis-password

# ===========================================
# REQUIRED FOR LOCAL DEVELOPMENT ONLY
# ===========================================

# Database URL (for local dev only; Docker Compose overrides this)
DATABASE_URL=postgresql://postgres:your-secure-db-password@localhost:5432/command_ops

# Authentication
BETTER_AUTH_SECRET=your-secret-key-here
BETTER_AUTH_URL=http://localhost:3000

# Google OAuth
# ===========================================
# OAUTH (Currently Required)
# ===========================================

# Google OAuth (get from Google Cloud Console)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# GitHub OAuth
# GitHub OAuth (get from GitHub Developer Settings)
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# Self-hosted Redis for Rate Limiting
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD= # Leave empty if no password
REDIS_DB=0 # Default database
# ===========================================
# OPTIONAL - ANALYTICS
# ===========================================


# PostHog Configuration
# PostHog Analytics (optional)
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=

# ===========================================
# LOCAL DEV REDIS SETTINGS (Not needed for Docker)
# ===========================================

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
114 changes: 114 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Self-Hosting Guide

Deploy Command Ops on your own infrastructure using Docker.

## Quick Start

1. **Clone and configure:**
```bash
git clone https://github.com/Yavnik/commandops.git
cd commandops
cp .env.example .env
```

2. **Edit your `.env` file:**
```bash
nano .env
```

Set these required variables:
- `NEXT_PUBLIC_BASE_URL` - Your domain (e.g., `https://commandops.yourdomain.com` or `http://192.168.1.100:3000`)
- `BETTER_AUTH_SECRET` - Random string, 32+ characters (generate with `openssl rand -base64 32`)
- `POSTGRES_PASSWORD` - Secure database password
- `REDIS_PASSWORD` - Secure Redis password

3. **Deploy:**
```bash
docker compose up -d --build
```

4. **Access your instance:**

Open `http://localhost:3000` (or your configured domain) and create your account.

That's it! Your self-hosted Command Ops is ready.

## Configuration

### Required Environment Variables

| Variable | Description | Example |
|----------|-------------|---------|
| `NEXT_PUBLIC_BASE_URL` | Public URL of your deployment | `https://commandops.example.com` |
| `BETTER_AUTH_SECRET` | Auth secret key (32+ chars) | `your-random-secret-key-here` |
| `POSTGRES_PASSWORD` | Database password | `secure_db_password` |
| `REDIS_PASSWORD` | Redis password | `secure_redis_password` |

### OAuth Login (Currently Required)

> **Note:** Google and GitHub OAuth credentials are currently required for authentication. We're working on making them optional for self-hosted instances. Stay tuned!

**Google OAuth:**

1. Create OAuth credentials at [Google Cloud Console](https://console.cloud.google.com/)
2. Add authorized origins and redirect URIs
3. Set in `.env`:
```bash
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
```

**GitHub OAuth:**

1. Create OAuth App at [GitHub Settings > Developer settings](https://github.com/settings/developers)
2. Set callback URL to `{YOUR_DOMAIN}/api/auth/callback/github`
3. Set in `.env`:
```bash
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
```

### Optional: Analytics

To enable PostHog analytics:
```bash
NEXT_PUBLIC_POSTHOG_KEY=your_posthog_key
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
```

## Maintenance

### View Logs

```bash
docker compose logs -f # All services
docker compose logs -f app # Application only
docker compose logs -f postgres # Database only
```

### Update to Latest Version

```bash
git pull
docker compose down
docker compose up -d --build
```

## What's Included

The `docker-compose.yml` sets up:
- **App**: Next.js application (exposed on port 3000)
- **PostgreSQL**: Database with persistent storage
- **Redis**: For rate limiting and caching
- **Migrations**: Automatic database schema setup on first run

All data is stored in Docker volumes and persists across restarts.

## Need Help?

- Check logs: `docker compose logs -f`
- Ensure all required environment variables are set
- Verify Docker and Docker Compose are up to date
- [Open an issue](https://github.com/Yavnik/commandops/issues) on GitHub

---
57 changes: 29 additions & 28 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
# Use Bun with Node.js compatibility
FROM oven/bun:1.2 AS base

# Install Node.js for tooling compatibility (drizzle-kit)
RUN apt-get update && apt-get install -y \
curl \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install dependencies only when needed
# Use Bun with Alpine for smaller base image
FROM oven/bun:1.2-alpine AS base

# Install dependencies for building
FROM base AS deps
WORKDIR /app

# Copy package files
COPY package.json bun.lock* ./

# Install all dependencies, including devDependencies for drizzle-kit
# Install all dependencies for build
RUN bun install --frozen-lockfile

# Rebuild the source code only when needed
# Build the application
FROM base AS builder
WORKDIR /app

Expand All @@ -29,7 +21,16 @@ COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY . .

# Build the application
# Ensure build doesn't require real runtime secrets
ENV NEXT_TELEMETRY_DISABLED=1
ENV CI=1
ENV DATABASE_URL=postgresql://dummy_user:dummy_password@postgres:5432/command_ops
ENV BETTER_AUTH_SECRET=BETTER_AUTH_SECRET
ENV GOOGLE_CLIENT_ID=GOOGLE_CLIENT_ID
ENV GOOGLE_CLIENT_SECRET=GOOGLE_CLIENT_SECRET
ENV GITHUB_CLIENT_ID=GITHUB_CLIENT_ID
ENV GITHUB_CLIENT_SECRET=GITHUB_CLIENT_SECRET
# Build Next.js (no server secrets passed at build time)
RUN bun run build

# Production image
Expand All @@ -40,7 +41,7 @@ WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs

# Copy built application
# Copy built application artifacts
COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
Expand All @@ -50,27 +51,27 @@ RUN mkdir .next && chown nextjs:nodejs .next
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Copy migration files and scripts
COPY --from=deps /app/node_modules ./node_modules
# Copy migrations and migrator script with dependencies
COPY --chown=nextjs:nodejs drizzle ./drizzle
COPY --chown=nextjs:nodejs src/db ./src/db
COPY --chown=nextjs:nodejs drizzle.config.ts ./
COPY --chown=nextjs:nodejs package.json ./
COPY --chown=nextjs:nodejs scripts/start.sh ./start.sh
COPY --chown=nextjs:nodejs scripts/migrate.ts ./scripts/migrate.ts

RUN chmod +x ./start.sh
# Copy node_modules needed for migration script
# The standalone build doesn't include these, so we copy them from deps
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/postgres ./node_modules/postgres
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/drizzle-orm ./node_modules/drizzle-orm

# Switch to non-root user
# Run as non-root
USER nextjs

EXPOSE 3000

ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Health check
# Health check using wget (already available in Alpine)
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD bun --version || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
Comment on lines +72 to +74

@coderabbitai coderabbitai Bot Oct 11, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Health check correctly implemented.

Using wget for the health check is appropriate for Alpine-based images, and the /api/health endpoint provides a lightweight verification that the app is responsive.

Verify that the health endpoint exists and returns a successful status:


🏁 Script executed:

#!/bin/bash
# Check if health endpoint exists
fd -e ts -e tsx "health" src/app/api/ --exec cat {}

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search entire repository for any occurrences of “health” in TS/JS files
rg -n 'health' -C2 -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx'

Length of output: 728


Add or correct the /api/health handler
No implementation for /api/health was found in the codebase; either create the API route (e.g. app/api/health/route.ts returning 200) or update the Docker HEALTHCHECK to use an existing endpoint.

🤖 Prompt for AI Agents
In Dockerfile around lines 72 to 74, the HEALTHCHECK targets /api/health which
has no handler in the codebase; either add a minimal API route at
app/api/health/route.ts that returns HTTP 200 (JSON or plain OK) for GET or
adjust the Dockerfile to point to an existing health endpoint. To add the route:
create app/api/health/route.ts exporting a GET handler that returns a 200
response quickly; if you prefer updating the Dockerfile, change the URL in the
HEALTHCHECK to the existing, implemented health/status endpoint and ensure path
and port match the running app.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The health check route already exists in app/api/health/route.ts

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the health check route exists and show its content
fd -e ts "route.ts" src/app/api/health/ --exec cat {}

Length of output: 69


@Yavnik, you're absolutely right, and I apologize for the incorrect comment. The health check route does exist at src/app/api/health/route.ts with a proper implementation:

export async function GET() {
  return Response.json({ ok: true });
}

The health check in the Dockerfile is correctly configured to use this endpoint. Thank you for the correction!


# Start the application with migrations
CMD ["./start.sh"]
# Start the application
CMD ["bun", "server.js"]
Loading