Skip to content

Deploying Bracket

Bracket is a modern, self-hosted tournament system designed to make organizing competitive events simple and intuitive. Built with async Python (FastAPI) backend and a React/Vite frontend using the Mantine UI library, Bracket provides comprehensive tournament management capabilities with beautiful, responsive interfaces. Whether you’re organizing esports competitions, sports tournaments, gaming events, or community competitions, Bracket offers the flexibility and control needed to manage tournaments of any scale with professional-grade features.

Why Bracket?

Bracket stands out as the premier choice for self-hosted tournament management with exceptional features:

  • Multiple Tournament Formats: Support for single elimination, round-robin, and swiss tournament formats
  • Flexible Tournament Structure: Build tournaments with multiple stages, groups, and brackets
  • Drag-and-Drop Interface: Easily reschedule matches between courts or time slots
  • Team Management: Create and manage teams, add players, and organize team information
  • Public Dashboards: Customizable dashboard pages for public display with logo support
  • Multi-Club Support: Create multiple clubs with multiple tournaments per club
  • Dynamic Swiss Tournaments: Automatic scheduling and management of swiss format tournaments
  • Match Scheduling: Intelligent match scheduling with conflict detection
  • Court Management: Assign matches to different courts or venues
  • Real-Time Updates: Live updates of match results and standings
  • Responsive Design: Works seamlessly on desktop and mobile devices
  • Multi-Language Support: Automatic language detection with Crowdin translations
  • API-First Architecture: Complete REST API for integrations
  • Self-Hosted Control: Complete data ownership and privacy
  • Open Source: AGPL-3.0 licensed with active community
  • PostgreSQL Backend: Robust database for tournament data
  • Docker Support: Easy deployment with Docker and Docker Compose
  • Standings Management: Automatic calculation and display of tournament standings
  • Player Statistics: Track player performance and statistics
  • Export Options: Export tournament data in multiple formats
  • Customizable Branding: Add logos and customize tournament appearance

Bracket is ideal for esports organizations hosting gaming tournaments, sports clubs managing competitive leagues, event organizers coordinating large tournaments, gaming communities running tournaments, educational institutions organizing competitions, and enterprises hosting corporate competitions. With persistent storage on Klutch.sh, your tournament management system is always available and secure.

Prerequisites

Before deploying Bracket, ensure you have:

  • A Klutch.sh account
  • A GitHub repository with your Bracket deployment configuration
  • Basic familiarity with Docker and Git
  • PostgreSQL database for tournament data storage
  • Sufficient storage for tournament data and assets (typically 10-50GB)
  • Understanding of tournament formats and structures
  • A custom domain for your tournament platform (recommended)

Important Considerations

Deploying Bracket

  1. Create a New Project

    Log in to your Klutch.sh dashboard and create a new project for your Bracket tournament system.

  2. Prepare Your Repository

    Create a GitHub repository with the following structure for your Bracket deployment:

    bracket-deploy/
    ├─ Dockerfile
    ├─ docker-compose.yml
    ├─ .env.example
    ├─ entrypoint.sh
    ├─ .gitignore
    └─ README.md

    Here’s a Dockerfile for Bracket backend:

    FROM python:3.11-slim
    WORKDIR /app
    # Install system dependencies
    RUN apt-get update && apt-get install -y \
    curl \
    git \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*
    # Clone Bracket repository
    RUN git clone https://github.com/evroon/bracket.git /tmp/bracket && \
    cp -r /tmp/bracket/* .
    # Install uv for Python package management
    RUN curl -LsSf https://astral.sh/uv/install.sh | sh
    # Install Python dependencies
    RUN ~/.local/bin/uv sync --frozen
    # Create necessary directories
    RUN mkdir -p /app/data \
    /app/logs \
    /app/uploads && \
    chmod -R 755 /app
    # Copy entrypoint script
    COPY entrypoint.sh /
    RUN chmod +x /entrypoint.sh
    # Expose port
    EXPOSE 8000
    # Health check
    HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1
    # Run entrypoint
    ENTRYPOINT ["/entrypoint.sh"]
    CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

    Create an entrypoint.sh file:

    #!/bin/bash
    set -e
    # Create necessary directories
    mkdir -p /app/data \
    /app/logs \
    /app/uploads
    # Set proper permissions
    chmod -R 755 /app
    echo "Initializing Bracket..."
    # Wait for PostgreSQL to be ready
    echo "Waiting for PostgreSQL..."
    until PGPASSWORD=$DATABASE_PASSWORD psql -h $DATABASE_HOST -U $DATABASE_USER -d postgres -c "\q" 2>/dev/null; do
    echo "PostgreSQL is unavailable - sleeping..."
    sleep 2
    done
    echo "PostgreSQL is up!"
    # Run database migrations
    echo "Running database migrations..."
    ~/.local/bin/uv run alembic upgrade head || true
    # Create admin user if specified
    if [ -n "$ADMIN_EMAIL" ] && [ -n "$ADMIN_PASSWORD" ]; then
    echo "Setting up admin user..."
    ~/.local/bin/uv run python -c "
    from app.models import User
    from app.database import get_db
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    import os
    engine = create_engine(os.getenv('DATABASE_URL'))
    Session = sessionmaker(bind=engine)
    session = Session()
    user = session.query(User).filter_by(email=os.getenv('ADMIN_EMAIL')).first()
    if not user:
    user = User(email=os.getenv('ADMIN_EMAIL'))
    user.set_password(os.getenv('ADMIN_PASSWORD'))
    session.add(user)
    session.commit()
    print(f'Created admin user: {os.getenv(\"ADMIN_EMAIL\")}')
    else:
    print(f'Admin user {os.getenv(\"ADMIN_EMAIL\")} already exists')
    " || true
    fi
    echo "Bracket is starting..."
    exec "$@"

    For the frontend, create a separate Dockerfile or use a multi-stage build. Here’s a simple Node.js based Dockerfile:

    FROM node:18-alpine
    WORKDIR /app
    # Install dependencies
    RUN npm install -g pnpm
    # Clone Bracket repository
    RUN git clone https://github.com/evroon/bracket.git /tmp/bracket && \
    cp -r /tmp/bracket/frontend/* .
    # Install frontend dependencies
    RUN pnpm install
    # Build frontend
    RUN pnpm run build
    # Expose port
    EXPOSE 3000
    # Start development server
    CMD ["pnpm", "run", "preview"]

    Create a .env.example file:

    Terminal window
    # Bracket Configuration
    BRACKET_TITLE=Bracket Tournament System
    BRACKET_DESCRIPTION=Manage your tournaments with ease
    # Database Configuration
    DATABASE_URL=postgresql://bracket:secure_password@postgres.internal:5432/bracket
    DATABASE_HOST=postgres.internal
    DATABASE_USER=bracket
    DATABASE_PASSWORD=secure_password
    DATABASE_NAME=bracket
    # Admin User
    ADMIN_EMAIL=admin@yourdomain.com
    ADMIN_PASSWORD=secure_admin_password
    ADMIN_NAME=Administrator
    # Server Configuration
    SERVER_HOST=0.0.0.0
    SERVER_PORT=8000
    API_BASE_URL=https://api.tournament.yourdomain.com
    # Frontend Configuration
    FRONTEND_URL=https://tournament.yourdomain.com
    VITE_API_URL=https://api.tournament.yourdomain.com
    # Security
    SECRET_KEY=your-secret-key-32-characters-long
    ALLOW_REGISTRATION=false
    REQUIRE_EMAIL_VERIFICATION=false
    # Email Configuration (optional)
    SMTP_SERVER=smtp.gmail.com
    SMTP_PORT=587
    SMTP_USERNAME=noreply@yourdomain.com
    SMTP_PASSWORD=app_password
    SMTP_FROM_NAME=Bracket Tournament
    # Tournament Settings
    DEFAULT_LANGUAGE=en
    TIMEZONE=UTC
    TOURNAMENT_TIMEZONE=UTC
    # Features
    ENABLE_API=true
    ENABLE_TOURNAMENT_EXPORT=true
    ENABLE_STANDINGS_EXPORT=true
    ALLOW_PUBLIC_TOURNAMENT_VIEW=true
    # Logging
    LOG_LEVEL=INFO
    LOG_FILE=/app/logs/bracket.log

    Create a .gitignore file:

    .env
    __pycache__/
    *.pyc
    .venv/
    data/
    logs/
    uploads/
    build/
    dist/
    *.egg-info/
    node_modules/
    .DS_Store
    *.db
    .idea/
    venv/

    Commit and push to your GitHub repository:

    Terminal window
    git init
    git add .
    git commit -m "Initial Bracket tournament system deployment"
    git remote add origin https://github.com/yourusername/bracket-deploy.git
    git push -u origin main
  3. Create a New App

    In the Klutch.sh dashboard:

    • Click “Create New App”
    • Select your GitHub repository containing the Dockerfile
    • Choose the branch (typically main or master)
    • Klutch.sh will automatically detect the Dockerfile in the root directory
  4. Configure Environment Variables

    Set up these essential environment variables in your Klutch.sh dashboard:

    VariableDescriptionExample
    BRACKET_TITLEPlatform titleBracket Tournament System
    DATABASE_URLPostgreSQL connectionpostgresql://user:password@host:5432/bracket
    DATABASE_HOSTDatabase hostpostgres.internal
    DATABASE_USERDatabase userbracket
    DATABASE_PASSWORDDatabase passwordsecure_password
    ADMIN_EMAILAdmin emailadmin@yourdomain.com
    ADMIN_PASSWORDAdmin passwordsecure_password
    SERVER_PORTAPI server port8000
    API_BASE_URLAPI endpointhttps://api.tournament.yourdomain.com
    FRONTEND_URLFrontend URLhttps://tournament.yourdomain.com
    SECRET_KEYSession encryption keyyour-secret-key
    ALLOW_REGISTRATIONAllow user signupfalse
    DEFAULT_LANGUAGELanguageen
    TIMEZONEServer timezoneUTC
    LOG_LEVELLogging verbosityINFO
  5. Configure Persistent Storage

    Bracket requires persistent storage for tournament data and uploads. Add persistent volumes:

    Mount PathDescriptionRecommended Size
    /app/dataTournament data and configuration50GB
    /app/logsApplication logs10GB
    /app/uploadsTournament media and assets100GB

    In the Klutch.sh dashboard:

    • Navigate to your app settings
    • Go to the “Volumes” section
    • Click “Add Volume” for each mount path
    • Set mount paths and sizes as specified above
    • Ensure uploads volume is sized for media files
  6. Set Network Configuration

    Configure your app’s network settings:

    • Select traffic type: HTTP
    • Recommended internal port: 8000 (FastAPI default)
    • Klutch.sh will handle HTTPS termination via reverse proxy
    • For frontend, you may need a separate deployment for the React app
  7. Configure Custom Domains

    Bracket benefits from custom domains for the API and frontend:

    • Navigate to your app’s “Domains” section in Klutch.sh
    • Add custom domain for API (e.g., api.tournament.yourdomain.com)
    • Add separate custom domain for frontend (e.g., tournament.yourdomain.com)
    • Configure DNS with CNAME records pointing to your Klutch.sh apps
    • Update API_BASE_URL and FRONTEND_URL environment variables
    • Klutch.sh will automatically provision SSL certificates
  8. Deploy Your App

    • Review all settings and environment variables carefully
    • Verify all persistent volumes are properly configured with adequate sizes
    • Ensure database connection string is correct
    • Click “Deploy”
    • Klutch.sh will build the Docker image and start your Bracket instance
    • Wait for the deployment to complete (typically 10-15 minutes)
    • Access your Bracket tournament system at your configured domain
    • Log in with admin credentials to begin creating tournaments

Initial Setup and Configuration

After deployment completes, access your Bracket instance to configure your tournament system.

Accessing Bracket

Navigate to your domain: https://tournament.yourdomain.com

Log in with the admin credentials you configured during setup.

Admin Dashboard Overview

Explore the main interface:

  1. Dashboard: Overview of active tournaments and statistics
  2. Clubs: Manage tournament clubs and organizations
  3. Tournaments: Create and manage tournaments
  4. Teams: Manage teams and players
  5. Settings: Configure system preferences
  6. Admin Panel: User and system administration

Creating a Club

Organize tournaments under clubs:

  1. Click “Clubs” in the navigation menu
  2. Click “Create New Club”
  3. Enter club information:
    • Club name
    • Club description
    • Logo/image (optional)
  4. Configure club settings
  5. Click “Create Club”

Creating a Tournament

Set up a new tournament:

  1. Navigate to your club
  2. Click “Create Tournament”
  3. Enter tournament details:
    • Tournament name
    • Description
    • Tournament format (Single Elimination, Round-Robin, Swiss)
    • Number of teams/participants
  4. Configure tournament settings:
    • Date and time
    • Location/venue
    • Visibility (public/private)
  5. Click “Create Tournament”

Building Tournament Structure

Design tournament brackets and stages:

  1. In tournament settings, go to “Structure”
  2. Add stages:
    • Qualifiers stage
    • Main stage
    • Finals stage
  3. For each stage, configure:
    • Number of groups/brackets
    • Advancement rules
    • Number of matches per round
  4. Set match scheduling preferences
  5. Save tournament structure

Managing Teams and Players

Add participants to your tournament:

  1. In tournament, click “Teams”
  2. “Add Teams” or “Import Teams”
  3. For each team, enter:
    • Team name
    • Team logo (optional)
    • Player roster
  4. Add player information:
    • Player name
    • Player ID/tag
    • Role (if applicable)
  5. Finalize team registration
  6. Save teams

Scheduling Matches

Organize tournament matches and timing:

  1. Navigate to tournament “Scheduling”
  2. Configure match settings:
    • Match duration
    • Break time between matches
    • Courts/venues available
  3. Generate automatic schedule or:
    • Manually create match schedule
    • Drag-and-drop to reschedule
    • Assign to specific courts
  4. Set match start times
  5. Publish schedule

Managing Match Results

Record match outcomes:

  1. In “Matches” or “Schedule” view
  2. Click on match to update
  3. Enter match results:
    • Winner/scores
    • Duration
    • Comments/notes
  4. If tournament auto-advances, standings update automatically
  5. Save result

Viewing Tournament Standings

Monitor tournament progress:

  1. Navigate to tournament “Standings”
  2. View current standings by:
    • Points/wins
    • Head-to-head
    • Tiebreakers
  3. Filter by group/bracket
  4. Export standings if needed

Public Dashboard Configuration

Share tournament progress publicly:

  1. Go to tournament “Dashboard” settings
  2. Configure public dashboard:
    • Visibility (public/private)
    • Information shown (standings, schedule, results)
    • Logo and branding
    • Color scheme
  3. Customize display:
    • Select dashboard widgets
    • Arrange information
    • Set refresh rate
  4. Generate public link
  5. Share with spectators/participants

Configuring Match Courts

Set up venue/court management:

  1. In tournament settings, go to “Courts”
  2. Add courts/venues:
    • Court name
    • Location
    • Capacity
    • Equipment details
  3. Assign matches to courts
  4. View court schedules
  5. Monitor court utilization

Exporting Tournament Data

Export tournament information:

  1. Go to tournament “Export”
  2. Select export format:
    • CSV (for spreadsheets)
    • JSON (for integrations)
    • PDF (for reports)
  3. Choose data to export:
    • Teams
    • Standings
    • Schedule
    • Results
  4. Download exported file

Managing Users and Access

Control user access to tournaments:

  1. Go to “Settings” → “Users”
  2. Add users:
    • Email address
    • Role (Admin, Manager, Viewer)
    • Tournament access
  3. Edit user permissions
  4. Remove users as needed
  5. Configure role permissions

Environment Variable Examples

Basic Configuration

Terminal window
BRACKET_TITLE=Bracket Tournament System
DATABASE_URL=postgresql://bracket:password@postgres.internal:5432/bracket
DATABASE_HOST=postgres.internal
DATABASE_USER=bracket
DATABASE_PASSWORD=secure_password
ADMIN_EMAIL=admin@yourdomain.com
ADMIN_PASSWORD=secure_password
SERVER_PORT=8000
API_BASE_URL=https://api.tournament.yourdomain.com
FRONTEND_URL=https://tournament.yourdomain.com

Complete Production Configuration

Terminal window
# Application Settings
BRACKET_TITLE=Competitive Tournament Platform
BRACKET_DESCRIPTION=Professional tournament management system
BRACKET_VERSION=2.2.5
# Database Configuration
DATABASE_URL=postgresql://bracket:very_secure_password_32_chars@postgres.internal:5432/bracket
DATABASE_HOST=postgres.internal
DATABASE_PORT=5432
DATABASE_USER=bracket
DATABASE_PASSWORD=very_secure_password_32_chars
DATABASE_NAME=bracket
DATABASE_SSL=true
# Admin User
ADMIN_EMAIL=admin@yourdomain.com
ADMIN_PASSWORD=very_secure_admin_password_32_chars
ADMIN_NAME=Tournament Administrator
# Server Configuration
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
WORKERS=4
API_BASE_URL=https://api.tournament.yourdomain.com
FRONTEND_URL=https://tournament.yourdomain.com
# Frontend Configuration
VITE_API_URL=https://api.tournament.yourdomain.com
VITE_APP_TITLE=Bracket
VITE_ENVIRONMENT=production
# Security
SECRET_KEY=very_secure_secret_key_32_characters_long
ALLOW_REGISTRATION=false
REQUIRE_EMAIL_VERIFICATION=true
PASSWORD_MIN_LENGTH=8
SESSION_TIMEOUT=3600
COOKIE_SECURE=true
CORS_ALLOWED_ORIGINS=https://tournament.yourdomain.com
# Tournament Settings
DEFAULT_LANGUAGE=en
AVAILABLE_LANGUAGES=en,fr,de,es,nl,zh
TIMEZONE=UTC
TOURNAMENT_TIMEZONE=UTC
TOURNAMENT_AUTO_ADVANCE=true
TOURNAMENT_ALLOW_MANUAL_MATCHES=true
# Features
ENABLE_API=true
ENABLE_TOURNAMENT_EXPORT=true
ENABLE_STANDINGS_EXPORT=true
ENABLE_SCHEDULE_EXPORT=true
ALLOW_PUBLIC_TOURNAMENT_VIEW=true
ALLOW_TEAM_REGISTRATION=false
ENABLE_BRACKETS_DRAW=true
ENABLE_SWISS_SYSTEM=true
# Email Configuration
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USE_TLS=true
SMTP_USERNAME=noreply@yourdomain.com
SMTP_PASSWORD=smtp_app_password
SMTP_FROM_NAME=Bracket Tournament System
SMTP_FROM_EMAIL=noreply@yourdomain.com
# File Upload Configuration
MAX_UPLOAD_SIZE=104857600
ALLOWED_FILE_TYPES=jpg,png,pdf,xlsx
UPLOAD_PATH=/app/uploads
MEDIA_RETENTION_DAYS=365
# Logging
LOG_LEVEL=INFO
LOG_FILE=/app/logs/bracket.log
LOG_MAX_BYTES=10485760
LOG_BACKUP_COUNT=10
LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s
# Performance
CACHE_ENABLED=true
CACHE_TTL=3600
DATABASE_POOL_SIZE=20
DATABASE_POOL_RECYCLE=3600

Sample Code and Getting Started

Python - Tournament Management Integration

# Bracket Tournament Management Integration
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from typing import List, Optional
from datetime import datetime
import os
class TournamentManager:
def __init__(self):
database_url = os.getenv('DATABASE_URL')
self.engine = create_engine(database_url)
self.SessionLocal = sessionmaker(bind=self.engine)
def create_tournament(
self,
club_id: int,
name: str,
description: str,
format: str, # 'single_elimination', 'round_robin', 'swiss'
start_date: datetime,
**kwargs
) -> dict:
"""Create a new tournament"""
session: Session = self.SessionLocal()
try:
from app.models import Tournament
tournament = Tournament(
club_id=club_id,
name=name,
description=description,
format=format,
start_date=start_date,
status='created',
**kwargs
)
session.add(tournament)
session.commit()
return {
'id': tournament.id,
'name': tournament.name,
'format': tournament.format,
'created_at': tournament.created_at
}
finally:
session.close()
def add_team_to_tournament(
self,
tournament_id: int,
team_id: int
) -> bool:
"""Add a team to a tournament"""
session: Session = self.SessionLocal()
try:
from app.models import TournamentTeam
tournament_team = TournamentTeam(
tournament_id=tournament_id,
team_id=team_id,
registered_at=datetime.now()
)
session.add(tournament_team)
session.commit()
return True
except Exception as e:
session.rollback()
print(f"Error adding team: {e}")
return False
finally:
session.close()
def create_match(
self,
tournament_id: int,
stage_id: int,
team1_id: int,
team2_id: int,
court_id: Optional[int] = None,
start_time: Optional[datetime] = None
) -> dict:
"""Create a match in tournament"""
session: Session = self.SessionLocal()
try:
from app.models import Match
match = Match(
tournament_id=tournament_id,
stage_id=stage_id,
team1_id=team1_id,
team2_id=team2_id,
court_id=court_id,
start_time=start_time or datetime.now(),
status='scheduled'
)
session.add(match)
session.commit()
return {
'id': match.id,
'team1_id': match.team1_id,
'team2_id': match.team2_id,
'status': match.status
}
finally:
session.close()
def update_match_result(
self,
match_id: int,
winner_id: int,
team1_score: int,
team2_score: int,
duration_minutes: int
) -> bool:
"""Update match result and auto-advance if needed"""
session: Session = self.SessionLocal()
try:
from app.models import Match, Standings
match = session.query(Match).filter_by(id=match_id).first()
if not match:
return False
match.winner_id = winner_id
match.team1_score = team1_score
match.team2_score = team2_score
match.duration_minutes = duration_minutes
match.status = 'completed'
match.completed_at = datetime.now()
# Update standings
standings = session.query(Standings).filter_by(
tournament_id=match.tournament_id,
team_id=winner_id
).first()
if standings:
standings.wins += 1
standings.points += 3
session.commit()
return True
except Exception as e:
session.rollback()
print(f"Error updating match: {e}")
return False
finally:
session.close()
def get_tournament_standings(self, tournament_id: int) -> List[dict]:
"""Get tournament standings"""
session: Session = self.SessionLocal()
try:
from app.models import Standings, Team
standings = session.query(Standings, Team).filter(
Standings.tournament_id == tournament_id
).join(Team).order_by(
Standings.points.desc(),
Standings.wins.desc()
).all()
return [
{
'team_id': s[0].team_id,
'team_name': s[1].name,
'wins': s[0].wins,
'losses': s[0].losses,
'points': s[0].points,
'position': i + 1
}
for i, s in enumerate(standings)
]
finally:
session.close()
def generate_schedule(self, tournament_id: int) -> bool:
"""Generate tournament schedule"""
session: Session = self.SessionLocal()
try:
from app.models import Tournament, TournamentTeam, Match
tournament = session.query(Tournament).filter_by(
id=tournament_id
).first()
if not tournament:
return False
teams = session.query(TournamentTeam).filter_by(
tournament_id=tournament_id
).all()
if tournament.format == 'round_robin':
# Generate round-robin schedule
matches_to_create = []
team_ids = [t.team_id for t in teams]
for i in range(len(team_ids)):
for j in range(i + 1, len(team_ids)):
match = Match(
tournament_id=tournament_id,
team1_id=team_ids[i],
team2_id=team_ids[j],
status='scheduled'
)
matches_to_create.append(match)
session.add_all(matches_to_create)
session.commit()
return True
except Exception as e:
session.rollback()
print(f"Error generating schedule: {e}")
return False
finally:
session.close()
# Usage example
if __name__ == "__main__":
manager = TournamentManager()
# Create tournament
tournament = manager.create_tournament(
club_id=1,
name="Spring Gaming Tournament",
description="Community gaming competition",
format="single_elimination",
start_date=datetime(2024, 3, 15)
)
print(f"Created tournament: {tournament}")

TypeScript - Frontend Tournament Display

// Bracket Tournament Display Component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
interface Tournament {
id: number;
name: string;
format: string;
status: string;
startDate: string;
}
interface Match {
id: number;
team1Id: number;
team2Id: number;
team1Score?: number;
team2Score?: number;
status: string;
startTime?: string;
}
interface Standings {
teamId: number;
teamName: string;
wins: number;
losses: number;
points: number;
position: number;
}
const TournamentDashboard: React.FC<{ tournamentId: number }> = ({ tournamentId }) => {
const [tournament, setTournament] = useState<Tournament | null>(null);
const [matches, setMatches] = useState<Match[]>([]);
const [standings, setStandings] = useState<Standings[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'schedule' | 'standings'>('standings');
useEffect(() => {
loadTournamentData();
}, [tournamentId]);
const loadTournamentData = async () => {
try {
const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8000';
const [tourRes, matchRes, standRes] = await Promise.all([
axios.get(`${apiUrl}/tournaments/${tournamentId}`),
axios.get(`${apiUrl}/tournaments/${tournamentId}/matches`),
axios.get(`${apiUrl}/tournaments/${tournamentId}/standings`)
]);
setTournament(tourRes.data);
setMatches(matchRes.data);
setStandings(standRes.data);
setLoading(false);
} catch (error) {
console.error('Failed to load tournament data:', error);
setLoading(false);
}
};
if (loading) {
return <div className="loading">Loading tournament data...</div>;
}
return (
<div className="tournament-dashboard">
<div className="tournament-header">
<h1>{tournament?.name}</h1>
<div className="tournament-info">
<span className="format">{tournament?.format}</span>
<span className="status">{tournament?.status}</span>
</div>
</div>
<div className="tabs">
<button
className={`tab ${activeTab === 'standings' ? 'active' : ''}`}
onClick={() => setActiveTab('standings')}
>
Standings
</button>
<button
className={`tab ${activeTab === 'schedule' ? 'active' : ''}`}
onClick={() => setActiveTab('schedule')}
>
Schedule
</button>
</div>
{activeTab === 'standings' && (
<div className="standings-container">
<table className="standings-table">
<thead>
<tr>
<th>Position</th>
<th>Team</th>
<th>Wins</th>
<th>Losses</th>
<th>Points</th>
</tr>
</thead>
<tbody>
{standings.map((standing) => (
<tr key={standing.teamId} className="standings-row">
<td className="position">{standing.position}</td>
<td className="team-name">{standing.teamName}</td>
<td className="wins">{standing.wins}</td>
<td className="losses">{standing.losses}</td>
<td className="points">{standing.points}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{activeTab === 'schedule' && (
<div className="schedule-container">
<div className="matches-list">
{matches.map((match) => (
<div key={match.id} className="match-card">
<div className="match-time">
{match.startTime && new Date(match.startTime).toLocaleString()}
</div>
<div className="match-result">
<div className="team team1">
<span className="team-id">{match.team1Id}</span>
{match.status === 'completed' && (
<span className="score">{match.team1Score}</span>
)}
</div>
<div className="vs">VS</div>
<div className="team team2">
<span className="team-id">{match.team2Id}</span>
{match.status === 'completed' && (
<span className="score">{match.team2Score}</span>
)}
</div>
</div>
<div className={`match-status ${match.status}`}>
{match.status.toUpperCase()}
</div>
</div>
))}
</div>
</div>
)}
</div>
);
};
export default TournamentDashboard;

Bash - Backup and Maintenance Script

#!/bin/bash
# Bracket Backup and Maintenance Script
BACKUP_DIR="/backups/bracket"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
# Create backup directory
mkdir -p $BACKUP_DIR
echo "Starting Bracket backup..."
# Backup PostgreSQL database
echo "Backing up PostgreSQL database..."
PGPASSWORD=$DATABASE_PASSWORD pg_dump \
-h $DATABASE_HOST \
-U $DATABASE_USER \
-d $DATABASE_NAME \
-F c -b -v -f $BACKUP_DIR/bracket_db_$TIMESTAMP.dump
gzip $BACKUP_DIR/bracket_db_$TIMESTAMP.dump
# Backup uploads and media
echo "Backing up uploads..."
tar -czf $BACKUP_DIR/bracket_uploads_$TIMESTAMP.tar.gz \
/app/uploads
# Backup application data
echo "Backing up application data..."
tar -czf $BACKUP_DIR/bracket_data_$TIMESTAMP.tar.gz \
/app/data \
--exclude='/app/data/cache' \
2>/dev/null || true
# Backup logs
echo "Backing up logs..."
tar -czf $BACKUP_DIR/bracket_logs_$TIMESTAMP.tar.gz \
/app/logs 2>/dev/null || true
# Cleanup old backups
echo "Cleaning up old backups..."
find $BACKUP_DIR -name "bracket_*" -mtime +$RETENTION_DAYS -delete
# Verify backups
echo "Verifying backups..."
for file in $BACKUP_DIR/*_$TIMESTAMP.*; do
if [ -f "$file" ]; then
size=$(du -h "$file" | awk '{print $1}')
echo "✓ Created: $(basename $file) ($size)"
fi
done
# Calculate total backup size
TOTAL_SIZE=$(du -sh $BACKUP_DIR | awk '{print $1}')
echo ""
echo "Backup completed at: $TIMESTAMP"
echo "Total backup size: $TOTAL_SIZE"
echo "Backup location: $BACKUP_DIR"
# Database maintenance
echo ""
echo "Performing database maintenance..."
PGPASSWORD=$DATABASE_PASSWORD vacuumdb \
-h $DATABASE_HOST \
-U $DATABASE_USER \
-d $DATABASE_NAME \
--analyze
# Optional: Upload to cloud storage
# aws s3 sync $BACKUP_DIR s3://your-bucket/bracket-backups/ \
# --exclude "*" --include "bracket_*_$TIMESTAMP.*" \
# --delete
# Optional: Send backup notification
# curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
# -d "{\"text\":\"Bracket backup completed: $(du -sh $BACKUP_DIR | awk '{print $1}')\"}"

cURL - API Integration Examples

Terminal window
# Bracket API Examples
# Get tournament details
curl -X GET https://api.tournament.yourdomain.com/tournaments/1 \
-H "Content-Type: application/json"
# List all tournaments in club
curl -X GET "https://api.tournament.yourdomain.com/clubs/1/tournaments" \
-H "Content-Type: application/json"
# Create new tournament
curl -X POST https://api.tournament.yourdomain.com/tournaments \
-H "Content-Type: application/json" \
-d '{
"club_id": 1,
"name": "Spring Tournament 2024",
"format": "single_elimination",
"start_date": "2024-03-15T10:00:00Z",
"max_teams": 16
}'
# Add team to tournament
curl -X POST https://api.tournament.yourdomain.com/tournaments/1/teams \
-H "Content-Type: application/json" \
-d '{
"team_id": 5
}'
# Get tournament matches
curl -X GET "https://api.tournament.yourdomain.com/tournaments/1/matches" \
-H "Content-Type: application/json"
# Create match
curl -X POST https://api.tournament.yourdomain.com/tournaments/1/matches \
-H "Content-Type: application/json" \
-d '{
"stage_id": 1,
"team1_id": 5,
"team2_id": 8,
"start_time": "2024-03-15T10:00:00Z",
"court_id": 1
}'
# Update match result
curl -X PUT https://api.tournament.yourdomain.com/matches/123 \
-H "Content-Type: application/json" \
-d '{
"winner_id": 5,
"team1_score": 2,
"team2_score": 1,
"duration_minutes": 45
}'
# Get tournament standings
curl -X GET https://api.tournament.yourdomain.com/tournaments/1/standings \
-H "Content-Type: application/json"
# Export tournament data
curl -X GET "https://api.tournament.yourdomain.com/tournaments/1/export?format=json" \
-H "Content-Type: application/json" \
-o tournament_data.json
# Get tournament schedule
curl -X GET https://api.tournament.yourdomain.com/tournaments/1/schedule \
-H "Content-Type: application/json"
# Reschedule match
curl -X PUT https://api.tournament.yourdomain.com/matches/123/reschedule \
-H "Content-Type: application/json" \
-d '{
"start_time": "2024-03-15T14:00:00Z",
"court_id": 2
}'

Tournament Management Best Practices

Tournament Planning

Organize successful tournaments:

  1. Bracket Design:

    • Choose appropriate format
    • Plan number of stages
    • Define advancement rules
    • Consider team count
  2. Scheduling:

    • Allow sufficient break times
    • Consider venue constraints
    • Manage court availability
    • Plan for delays
  3. Team Management:

    • Collect team information early
    • Verify player eligibility
    • Set registration deadlines
    • Manage substitutions

Real-Time Management

Efficiently run tournaments:

  1. Monitor all matches
  2. Update results promptly
  3. Handle disputes professionally
  4. Manage delays and rescheduling
  5. Communicate with participants

Public Presentation

Share tournament progress:

  1. Configure public dashboard
  2. Update standings in real-time
  3. Display schedule prominently
  4. Stream matches if possible
  5. Share final results

Troubleshooting

Common Issues and Solutions

Issue: Cannot access tournament system

Solutions:

  • Verify Klutch.sh app is running
  • Check domain DNS resolution
  • Verify SSL certificate validity
  • Check network connectivity

Issue: Database connection failed

Troubleshooting:

  • Verify PostgreSQL is running
  • Check database credentials
  • Ensure database exists
  • Verify firewall rules
  • Check database logs

Issue: Matches not scheduling

Solutions:

  • Verify all teams registered
  • Check tournament structure
  • Verify stage configuration
  • Review scheduling rules
  • Check for conflicts

Issue: Slow performance

Solutions:

  • Optimize database queries
  • Check server resources
  • Monitor database size
  • Clear unused data
  • Enable caching

Updating Bracket

To update Bracket to a newer version:

  1. Backup your database and data
  2. Update your Dockerfile to latest version
  3. Commit and push to GitHub
  4. Klutch.sh will automatically rebuild
  5. Test all tournament functionality
  6. Verify data integrity
  7. Monitor logs for any issues

Use Cases

Esports Tournaments

  • Gaming competition organization
  • Multiple game titles
  • Regional and national tournaments

Sports Leagues

  • Team sports management
  • Season scheduling
  • Standings tracking

Community Events

  • Gaming meetups
  • Competitive gaming
  • Community tournaments

Educational Competitions

  • School tournaments
  • Academic competitions
  • Club championships

Additional Resources

Conclusion

Deploying Bracket on Klutch.sh provides you with a powerful, self-hosted tournament management system that supports multiple tournament formats and structures. With drag-and-drop scheduling, team management, real-time standings, customizable dashboards, and complete data control, Bracket enables you to organize professional tournaments with ease. Klutch.sh’s managed infrastructure ensures your tournament system is always available, secure, and performant, allowing you to focus on running great tournaments.

Start managing tournaments today by deploying Bracket on Klutch.sh and experience the power of a dedicated tournament management platform.