Thanks for your interest in contributing! Whether it's a bug fix, new feature, documentation improvement, or something else — all contributions are welcome.
Questions or ideas? Start a GitHub Discussion or open an issue.
Drydock maintains strict quality gates (100% code coverage, multi-stage CI pipeline, mutation testing). You don't need to worry about any of that. Here's how it works:
- You write the code — focus on the feature or fix itself
- Open a PR — even if it's rough, incomplete, or has no tests
- The maintainer handles the rest — testing, coverage, lint fixes, docs updates, and final polish
Your commits keep your Git author attribution. If the maintainer needs to restructure your work, they'll use Co-Authored-By to preserve credit.
The goal is zero friction for contributors. Don't let the CI pipeline scare you — it runs on PRs for visibility, but passing everything is the maintainer's job, not yours.
Drydock moves fast — open issues tend to get fixed quickly. The best way to find work:
- Open a Discussion and say what you're interested in. We'll scope something that fits your experience level.
- Browse the Ideas category — feature requests from users that haven't been built yet.
- Add a new trigger provider — the
app/triggers/providers/directory has 20 examples to follow. Adding support for a new notification service (Pushbullet, Gotify, etc.) is self-contained and well-patterned. - Documentation — improvements to
content/docs/are always welcome and need zero backend knowledge. - Check for
good first issuelabels when they exist, but don't wait for them.
-
Fork the repository and clone your fork
-
Use Node.js 24+:
nvm use || nvm install -
Install dependencies — each workspace manages its own:
cd app && npm install cd ui && npm install
-
Create a branch from the appropriate base:
- Bug fixes: branch from
main - New features: branch from the active feature branch (check open branches)
- Bug fixes: branch from
npm run build # TypeScript compilation
npx vitest run path/to/file.test.ts # Run a single test file (fast)
npx vitest run --reporter=verbose # Run all tests (no coverage)
npm run lint:fix # Auto-fix formattingnpm run serve # Dev server on port 8080
npx vitest run tests/path/to/file.spec.ts # Single test file
npm run lint:fix # Auto-fix formattingdocker build -t drydock:dev .
docker compose -f test/qa-compose.yml up -d # Starts on port 3333You don't need to run the full test suite, coverage gates, or e2e tests locally. Just make sure your code compiles (npm run build) and your specific tests pass. The maintainer handles the rest.
Drydock is a Docker container update manager with a dynamic component registry:
app/ # Backend (TypeScript, Express, LokiJS)
├── watchers/providers/ # Monitor containers (Docker socket)
├── registries/providers/ # Query image registries (23 providers)
├── triggers/providers/ # Send notifications / actions (20 providers)
├── api/ # REST API + SSE
├── store/ # LokiJS in-memory database
├── model/ # TypeScript interfaces
└── agent/ # Distributed controller-agent architecture
ui/ # Frontend (Vue 3, Tailwind CSS 4, Vite)
├── src/views/ # Page components
├── src/components/ # Shared components (AppButton, AppBadge, etc.)
├── src/composables/ # Vue composables
├── src/services/ # API client layer
└── src/utils/ # Helpers and mappers
content/docs/ # Documentation (MDX, versioned)
e2e/ # End-to-end tests (Cucumber + Playwright)
Component registry pattern: Components are loaded dynamically from environment variables:
DD_REGISTRY_GHCR_PRIVATE_TOKEN=xxx → loads registries/providers/ghcr/Ghcr.ts
DD_TRIGGER_SLACK_MYSLACK_TOKEN=xxx → loads triggers/providers/slack/Slack.ts
DD_WATCHER_LOCAL_SOCKET=xxx → loads watchers/providers/docker/Docker.ts
Each component type extends a base class with init(), deregister(), and type-specific methods.
- Language: TypeScript (ESM,
NodeNextmodule resolution) - Linter/formatter: Biome — run
npm run lint:fixto auto-fix - Line width: 100, single quotes
- No transpiler: compiles with
tscdirectly
We use Gitmoji + Conventional Commits:
<emoji> <type>(<scope>): <description>
| Emoji | Type | Use |
|---|---|---|
| ✨ | feat |
New feature |
| 🐛 | fix |
Bug fix |
| 📝 | docs |
Documentation |
| 💄 | style |
UI/cosmetic changes |
| ♻️ | refactor |
Code refactor (no feature/fix) |
| ⚡ | perf |
Performance improvement |
| ✅ | test |
Adding/updating tests |
| 🔧 | chore |
Build, config, tooling |
| 🔒 | security |
Security fix |
| ⬆️ | deps |
Dependency upgrade |
| 🗑️ | revert |
Intentional revert |
Scope is optional. Subject line: imperative, lowercase, no trailing period.
✨ feat(docker): add health check endpoint
🐛 fix: resolve socket EACCES (#38)
Don't stress about getting the emoji/format perfect — the commit-msg hook will tell you if something's off, and the maintainer can fix it during merge.
Tests are welcome but not required in your PR. The maintainer will add or update tests to maintain 100% coverage.
If you do want to write tests:
-
Framework: Vitest with globals — no need to import
describe,test,expect, orvi -
Run your test:
npx vitest run path/to/your.test.ts -
Logger mock (backend tests usually need this):
vi.mock('../../log/index.js', () => ({ default: { child: () => ({ info: vi.fn(), warn: vi.fn(), debug: vi.fn(), error: vi.fn() }) }, }));
-
Gotcha:
vi.mock()factories are hoisted above imports — you can't use imported helpers inside them. Usevi.hoisted()for values needed in mock factories.
- Target:
mainfor bug fixes, the active feature branch for new features - Size: Smaller is better — one concern per PR when possible
- Tests/coverage: Nice to have, not required. The maintainer handles it.
- Docs: If your change affects user-facing behavior, a docs update in the same PR is appreciated but not mandatory.
- CI failures: Don't worry about them. CI runs the full pipeline for visibility, but passing is the maintainer's responsibility.
Draft PRs are welcome if you want early feedback before finishing.
Open a GitHub Issue with steps to reproduce.
Do not open a public issue. See SECURITY.md for responsible disclosure instructions.
By contributing, you agree that your contributions will be licensed under the GNU Affero General Public License v3.0.
Full quality pipeline (maintainers only)
Lefthook runs a piped (sequential, fail-fast) pipeline on every git push:
| Priority | Step | What it does | On Failure |
|---|---|---|---|
| 1 | clean-tree |
Rejects uncommitted changes | Fail |
| 2 | ts-nocheck |
Checks for @ts-nocheck directives |
Fail |
| 3 | biome check |
Linting and formatting | Fail |
| 4 | qlty |
Static analysis (medium+ severity gate) | Fail |
| 5 | coverage |
Sharded app+ui parallel vitest with 100% threshold | Fail |
| 6 | build |
Sharded app+ui parallel tsc/vite (no tests) | Fail |
| 7 | e2e |
End-to-end Cucumber tests | Fail |
| 8 | e2e-playwright |
Playwright browser tests | Fail |
| 9 | zizmor |
GitHub Actions security scanning | Fail |
The pre-commit hook runs a scoped vitest --changed on staged workspaces for fast feedback. Full 100% coverage enforcement happens in the pre-push coverage step; on failure it writes .coverage-gaps.json with per-file metrics plus uncovered line numbers and branch ids parsed from lcov.info.
100% line/branch/function/statement coverage is enforced for both app/ and ui/. This is achievable because the project uses AI-assisted development for test generation. External contributors are not expected to meet this bar.
Stryker runs monthly (.github/workflows/quality-mutation-monthly.yml), advisory only. Use it as a quality signal, not a score target.
Snyk (Open Source, Code, Container, IaC) runs weekly via .github/workflows/security-snyk-weekly.yml to preserve monthly quotas.