A Beginner's Guide
This guide walks through the full club pipeline end to end. By the time you’re done, you’ll have a running club server, a package you published from your laptop, and a second project that depends on it.
Overview
You’ll go through four steps, in order:
- Self-host club with Docker — run the server locally using a
docker-compose.ymlthat pulls the official image. - Install the CLI — one-line install of the
clubbinary. - Publish your first package — log in, create a package, and push it to your server.
- Use your package in another project — add the published package as a dependency and import it.
A fifth section at the end shows how to automate publishing with GitHub Actions. Skip it on the first pass if you just want the happy path.
Prerequisites
- Docker and Docker Compose (for the server)
- Dart SDK 3.0+ (for creating and publishing packages)
- Linux or macOS (the install script doesn’t support Windows — see the CLI installation guide for the manual path)
Throughout this guide, the local server runs at http://localhost:8080. In production you’d replace that with a real HTTPS URL.
Step 1 — Self-host club with Docker
The quickest way to get a working server is a two-file Docker Compose setup that pulls the official image from GitHub Container Registry. No repo clone required.
-
Create a project directory
Terminal window mkdir -p ~/club-server && cd ~/club-server -
Create
docker-compose.ymlservices:club:image: ghcr.io/birjuvachhani/club:latestcontainer_name: clubrestart: unless-stoppedports:- "127.0.0.1:8080:8080"env_file: .envvolumes:- club_data:/datahealthcheck:test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]interval: 30stimeout: 5sretries: 3start_period: 10svolumes:club_data: -
Create
.envTerminal window cat > .env <<EOFSERVER_URL=http://localhost:8080JWT_SECRET=$(openssl rand -hex 32)EOF -
Start the server
Terminal window docker compose up -d -
Verify it’s running
Terminal window curl http://localhost:8080/api/v1/health# {"status":"ok", ...} -
Create the owner account via the setup wizard
On first startup the server prints a one-time setup code to the logs. You use it to create the initial owner account through the web UI.
Terminal window docker compose logs club | grep -i "setup code"Open http://localhost:8080/setup, enter the setup code, then pick your email, display name, and password. The account is created with the
ownerrole.
Step 2 — Install the CLI
The club CLI is a native binary. One-line install:
curl -fsSL https://club.birju.dev/install.sh | bashThe script detects your OS and CPU, downloads the right archive, verifies its SHA-256, and drops the binary at ~/.local/bin/club. If that directory isn’t on your PATH, the installer prints the one-liner to add to your shell’s rc file.
Verify:
club --versionFor Windows, manual downloads, version pinning, or building from source, see the CLI installation guide.
Step 3 — Publish your first package
With the server running and the CLI installed, you’ll now log in, create a small package, and push it up.
-
Log in to your server
Terminal window club login http://localhost:8080This opens your browser to complete an OAuth authorization flow (PKCE). Sign in with the owner account you created in Step 1, approve the CLI’s
read,writerequest, and the CLI stores a scoped personal access token in~/.config/club/credentials.json. -
Register the token with
dart pubTerminal window club setupBehind the scenes this runs
dart pub token add http://localhost:8080, so the Dart SDK knows how to authenticate when you publish. -
Create a package
Terminal window mkdir -p ~/hello_club && cd ~/hello_clubCreate
pubspec.yaml:name: hello_clubversion: 1.0.0description: My first club package.publish_to: http://localhost:8080environment:sdk: ^3.0.0Create
lib/hello_club.dart:/// A friendly greeting.String greet(String name) => 'Hello, $name!'; -
Publish
You have two options:
Terminal window # Option A: the club CLI (runs ~25 preflight validators)club publish# Option B: vanilla dart pubdart pub publish --forceclub publishis the recommended path — it validates your package before upload (changelog, SDK constraints, README, dependencies, git status, size, etc.) and works without needingpublish_to:inpubspec.yaml(it picks up your default club server automatically).Open http://localhost:8080 in your browser —
hello_club1.0.0 should appear on the packages page.
For the longer walkthrough with READMEs, changelogs, and troubleshooting, see Publishing Your First Package and the Publishing guide.
Step 4 — Use your package in another project
You publish packages so other projects can depend on them. Any developer who wants to consume packages from your club server goes through the same CLI setup: install, log in, then add a hosted dependency.
-
Install and log in (skip if you already did steps 2–3 above)
Terminal window curl -fsSL https://club.birju.dev/install.sh | bashclub login http://localhost:8080club setup -
Create a project
Terminal window mkdir -p ~/my_app && cd ~/my_appdart create -t console . -
Add
hello_clubas a hosted dependencyEdit
pubspec.yaml:dependencies:hello_club:hosted: http://localhost:8080version: ^1.0.0The
hostedfield tellsdart pubto fetch this package from your club server. All other dependencies (nohostedfield) continue to come from pub.dev as normal. -
Fetch and use it
Terminal window dart pub getEdit
bin/my_app.dart:import 'package:hello_club/hello_club.dart';void main() {print(greet('Developer'));}Terminal window dart run# Hello, Developer!
For version pinning, upgrading, and mixing private and public packages, see Using Packages.
Bonus — Automate with GitHub Actions
In CI you don’t want to log in interactively — you want a scoped, machine-owned token. The pattern is:
- Create an API key in the web dashboard
- Store it as a CI secret
- Register it with
dart pubat runtime using--env-var
Requirements
- A club API key with the right scopes
readonly — if CI just runsdart pub get/ testsread+write— if CI runsdart pub publish
- The secret stored in your CI provider’s secret store (GitHub Actions: Settings → Secrets and variables → Actions)
- A reachable server URL (CI runners must be able to reach your club server)
Create the API key
- Open the web UI and go to Settings → API keys (the
/settings/keyspage). - Click Create key, give it a name like
github-actions-publish, and select the scopes you need (read+writefor a publish workflow). Scopes are clamped to your user role — amembercannot mintadmintokens. - Optionally set an expiry (90 days is a good default — rotate rather than forget).
- Copy the full key (
club_pat_...) — it is only shown once. - In GitHub, add it as a repository secret named
CLUB_TOKEN.
Publishing workflow
This workflow publishes a new version whenever you push a v* tag.
name: Publishon:push: tags: - 'v*'
jobs:publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1
- name: Register club token with dart pub run: dart pub token add https://club.example.com --env-var CLUB_TOKEN env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}
- name: Publish run: dart pub publish --force env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}The key step is dart pub token add ... --env-var CLUB_TOKEN. The --env-var form does not write the token to disk — it tells dart pub to read it from the named environment variable at runtime, which is exactly what you want in CI.
Typical release flow
-
Bump the
versioninpubspec.yaml. -
Commit and tag:
Terminal window git commit -am "Release v1.1.0"git tag v1.1.0git push origin main --tags -
GitHub Actions picks up the tag, runs the workflow, and the new version appears on your club server.
For GitLab, Bitbucket, caching, and security best practices, see the full CI/CD Integration guide.
Where to go next
You now have the complete pipeline: a server, a published package, a consuming project, and automated publishing. Good follow-ups:
- Publishers — group packages under teams with member roles
- Managing users — invite developers, reset passwords, manage roles
- Package scoring — enable pana analysis for health scores
- Authentication — tokens, scopes, and credential management in depth
- Production self-hosting — persistent volumes, TLS, PostgreSQL, S3