Skip to main content
This guide will help you deploy Better Uptime using Docker and set up your first monitor in under 10 minutes.

Prerequisites

Before you begin, ensure you have the following installed:
  • Docker and Docker Compose (v2.0+)
  • PostgreSQL database (local or cloud-hosted)
  • GitHub OAuth App for authentication (Create one here)
  • Resend API Key for email notifications (Get one here)
Optional: Set up ClickHouse for time-series metrics storage. Better Uptime will work without it, but historical data will be limited.

Environment Setup

Create a .env file in your project root with the following configuration:
.env
# Database
DATABASE_URL=postgresql://user:password@host:5432/database

# Authentication
JWT_SECRET=your-strong-jwt-secret-here

# GitHub OAuth
CLIENT_ID_GITHUB=your-github-client-id
CLIENT_SECRET_GITHUB=your-github-client-secret

# Email (Resend)
RESEND_API_KEY=your-resend-api-key

# Redis Configuration
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_USERNAME=default
REDIS_PASSWORD=dev-password

# Backend
BACKEND_PORT=8084

# Frontend
FRONTEND_PORT=3000
NEXT_PUBLIC_BACKEND_URL=http://localhost:8084

# Worker Configuration
REGION_ID=us-east-1
WORKER_ID=worker-1

# ClickHouse (optional)
CLICKHOUSE_URL=
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=
CLICKHOUSE_DATABASE=default
CLICKHOUSE_METRICS_TABLE=uptime_checks
Never commit your .env file to version control. Add it to .gitignore immediately.

Docker Setup

Better Uptime provides two Docker configurations: development and production. Choose the one that fits your needs.

Development Setup

The development configuration includes hot reload and is optimized for local development.
1

Navigate to Docker directory

cd docker
2

Start development services

docker compose -f docker-compose.dev.yaml up --build
This will start:
  • Redis - Self-hosted Redis for streams
  • Backend - Bun server with tRPC API
  • Frontend - Next.js client with hot reload
  • Worker - Processes uptime checks from Redis streams
  • Publisher - Publishes website checks every 3 minutes
3

Access the application

Development Features

  • Hot reload for all services (Bun --hot flag, Next.js dev mode)
  • Volume mounts for live code changes
  • Separate networks and volumes (better-uptime-network-dev)
  • Development-friendly defaults

Useful Commands

docker compose -f docker-compose.dev.yaml up -d

Setting Up Your First Monitor

Once Better Uptime is running, you can create your first website monitor through the API or UI.

Using the API

1

Authenticate

Sign in through GitHub OAuth at http://localhost:3000
2

Create a monitor

Use the tRPC API to register a website:
// Example using the tRPC client
const website = await trpc.website.register.mutate({
  url: "https://example.com",
  name: "My Website" // optional
});
The API validates URLs to ensure they use HTTP/HTTPS protocol:
// From source: packages/validators/src/website/index.ts
const httpUrlSchema = z
  .string()
  .min(1, "URL is required")
  .url("Invalid URL format")
  .refine(
    (url) => {
      const parsed = new URL(url);
      return parsed.protocol === "http:" || parsed.protocol === "https:";
    },
    { message: "URL must use HTTP or HTTPS protocol" }
  );
3

Monitor starts automatically

The website is immediately published to Redis streams for the first check:
// From source: packages/api/src/routes/website.ts:56-69
// Immediate first check: publish website immediately once
// Then let periodic publisher handle rest
await xAddBulk([{ url: website.url, id: website.id }]);
After that, the publisher service checks the website every 3 minutes.

Monitor Data Structure

When you create a monitor, the following data is stored:
// Input validation
{
  url: "https://example.com",     // Required: HTTP/HTTPS URL
  name: "My Website"               // Optional: Max 255 characters
}

// Output after creation
{
  id: "uuid",
  url: "https://example.com",
  name: "My Website",
  isActive: true,
  userId: "user-uuid",
  createdAt: Date,
  updatedAt: Date
}

Viewing Monitor Status

Retrieve the status of your monitors:
// Get recent individual checks (last 90)
const status = await trpc.website.status.query({
  viewMode: "per-check"
});
The response includes status points with health data:
{
  websites: [
    {
      websiteId: "uuid",
      websiteName: "My Website",
      websiteUrl: "https://example.com",
      statusPoints: [
        {
          status: "UP",           // or "DOWN"
          checkedAt: Date,
          responseTimeMs: 123,
          httpStatusCode: 200
        }
      ],
      currentStatus: {
        status: "UP",
        checkedAt: Date,
        responseTimeMs: 123,
        httpStatusCode: 200,
        regionId: "us-east-1"
      }
    }
  ]
}

System Architecture

Better Uptime uses a modern tech stack for performance and reliability:
  • Backend: tRPC for type-safe APIs
  • Time-Series Data: ClickHouse for metrics storage
  • Streams: Redis for real-time uptime checks
  • Monorepo: Turborepo for efficient builds
  • Runtime: Bun for fast JavaScript execution
  • Database: Prisma ORM with PostgreSQL
  • Validation: Zod for schema validation

View Full Architecture

Explore the complete system architecture and data flow diagrams

Next Steps

Configure Monitoring

Learn about advanced monitoring options and configurations

Set Up Notifications

Configure email and webhook notifications for downtime alerts

API Reference

Explore the complete tRPC API documentation

Deploy to Production

Best practices for production deployment and scaling

Troubleshooting

If services can’t connect to Redis:
  1. Check Redis is healthy: docker compose ps
  2. Verify Redis password matches in .env
  3. Check network connectivity: docker compose exec backend ping redis
  4. View Redis logs: docker compose logs redis
If ports are already in use:
  1. Change ports in .env file
  2. Update docker-compose.yaml port mappings
  3. Restart services: docker compose restart
For development environments:
  1. Verify volume mounts: docker compose config
  2. Check file permissions on mounted volumes
  3. Restart the service: docker compose restart backend
If Docker builds fail:
  1. Clear Docker cache: docker compose build --no-cache
  2. Verify all package.json files exist
  3. Ensure pnpm-lock.yaml is up to date

Build docs developers (and LLMs) love