From-Source Deployment
This guide covers running club directly on a server without Docker. Use this approach if you prefer not to use containers, need to customise the build, or want to run club on a system where Docker is not available.
Prerequisites
- A Linux server (Debian/Ubuntu recommended, but any systemd-based distro works)
- Dart SDK >= 3.7
- Node.js >= 22
- Git
Building from Source
-
Clone the repository
Terminal window git clone https://github.com/BirjuVachhani/club.gitcd club -
Install Dart dependencies
Terminal window dart pub get -
Run code generation (club_core only)
Terminal window cd packages/club_coredart run build_runner build --delete-conflicting-outputscd ../.. -
Build the SvelteKit frontend
Terminal window cd packages/club_webnpm installnpm run buildcd ../..This produces static HTML/JS/CSS in
packages/club_web/build/. -
Build the server binary
Terminal window dart build cli -t packages/club_server/bin/server.dart -o build/serverThe result is a self-contained native executable:
build/server/└── bundle/├── bin/│ └── server # AOT-compiled binary (entrypoint)└── lib/ # Dynamic libraries the binary loads at runtimeBoth
bundle/bin/andbundle/lib/must be kept together. Do not copy the binary alone.
Installation Layout
Set up a clean directory structure for the deployment:
# Create the club usersudo useradd --system --home-dir /opt/club --create-home --shell /usr/sbin/nologin club
# Create directoriessudo mkdir -p /opt/clubsudo mkdir -p /opt/club/static/websudo mkdir -p /var/lib/club/packagessudo mkdir -p /var/lib/club/docssudo mkdir -p /etc/club
# Copy the build output — keep bin/ and lib/ togethersudo cp -r build/server/bundle/. /opt/club/
# Copy the frontend static filessudo cp -r packages/club_web/build/. /opt/club/static/web/
# Set ownershipsudo chown -R club:club /opt/clubsudo chown -R club:club /var/lib/clubThe layout after installation:
/opt/club/├── bin/│ └── server # AOT-compiled binary├── lib/ # Dynamic libraries loaded by the binary└── static/ └── web/ # SvelteKit static export
/var/lib/club/├── club.db # SQLite database (created on first run)├── packages/ # Package tarballs└── docs/ # Generated dartdoc output
/etc/club/└── env # Environment variablesConfiguration
Create the environment file at /etc/club/env:
sudo tee /etc/club/env << 'EOF'SERVER_URL=https://packages.example.comJWT_SECRET=<output of: openssl rand -hex 32>TRUST_PROXY=true
SQLITE_PATH=/var/lib/club/club.dbBLOB_PATH=/var/lib/club/packagesDARTDOC_PATH=/var/lib/club/docsSTATIC_FILES_PATH=/opt/club/static/web
LISTEN_PORT=8080HOST=127.0.0.1EOFRestrict permissions (the file contains secrets):
sudo chmod 600 /etc/club/envsudo chown club:club /etc/club/envGenerate the JWT secret:
sudo sed -i "s|<output of: openssl rand -hex 32>|$(openssl rand -hex 32)|" /etc/club/envSystemd Service
Create /etc/systemd/system/club.service:
[Unit]Description=club Dart Package RepositoryAfter=network.targetDocumentation=https://github.com/BirjuVachhani/club
[Service]Type=simpleUser=clubGroup=clubExecStart=/opt/club/bin/serverEnvironmentFile=/etc/club/envWorkingDirectory=/opt/club
# Restart on failureRestart=on-failureRestartSec=5
# Security hardeningNoNewPrivileges=trueProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/clubPrivateTmp=true
# Resource limitsLimitNOFILE=65536
[Install]WantedBy=multi-user.targetEnable and start the service:
sudo systemctl daemon-reloadsudo systemctl enable clubsudo systemctl start clubCheck the status:
sudo systemctl status clubRunning as a Background Service
Starting
sudo systemctl start clubStopping
sudo systemctl stop clubRestarting
sudo systemctl restart clubChecking status
sudo systemctl status clubViewing the setup code on first run
sudo journalctl -u club | grep -i 'setup code'Paste the code into the /setup wizard in your browser to create the initial admin account.
Log Management
Viewing logs
# Follow logs in real timesudo journalctl -u club -f
# Last 100 linessudo journalctl -u club -n 100
# Logs since a specific timesudo journalctl -u club --since "2025-01-15 10:00:00"
# Logs from the current bootsudo journalctl -u club -bLog rotation
systemd journal handles log rotation automatically. To configure retention:
# Edit /etc/systemd/journald.confsudo tee -a /etc/systemd/journald.conf << 'EOF'SystemMaxUse=500MMaxRetentionSec=30dayEOF
sudo systemctl restart systemd-journaldForwarding to a file
If you prefer file-based logs (for integration with log aggregation tools):
# Edit the service to log to a filesudo mkdir -p /var/log/clubsudo chown club:club /var/log/clubAdd to the [Service] section of the systemd unit:
StandardOutput=append:/var/log/club/club.logStandardError=append:/var/log/club/club.logSet up logrotate at /etc/logrotate.d/club:
/var/log/club/*.log { daily rotate 14 compress delaycompress missingok notifempty create 640 club club postrotate systemctl reload club 2>/dev/null || true endscript}Upgrading
-
Back up the database
Terminal window sqlite3 /var/lib/club/club.db ".backup /var/lib/club/club-backup-$(date +%Y%m%d).db" -
Pull the latest code and rebuild
Terminal window cd /path/to/clubgit pulldart pub getcd packages/club_coredart run build_runner build --delete-conflicting-outputscd ../..cd packages/club_webnpm installnpm run buildcd ../..dart build cli -t packages/club_server/bin/server.dart -o build/server -
Replace the binary, libraries, and static files
Terminal window sudo systemctl stop clubsudo cp -r build/server/bundle/. /opt/club/sudo cp -r packages/club_web/build/. /opt/club/static/web/sudo chown -R club:club /opt/club -
Restart the service
Terminal window sudo systemctl start clubDatabase migrations run automatically on startup.
-
Verify
Terminal window sudo systemctl status clubcurl -sf http://localhost:8080/api/v1/health | jq .
Backups
SQLite backup (safe while running)
sqlite3 /var/lib/club/club.db ".backup /opt/backups/club-$(date +%Y%m%d).db"Package tarballs
tar czf /opt/backups/club-packages-$(date +%Y%m%d).tar.gz -C /var/lib/club/packages .Automated daily backup via cron
sudo tee /etc/cron.d/club-backup << 'EOF'0 2 * * * club sqlite3 /var/lib/club/club.db ".backup /opt/backups/club-$(date +\%Y\%m\%d).db" && tar czf /opt/backups/club-packages-$(date +\%Y\%m\%d).tar.gz -C /var/lib/club/packages .EOF