Contributing to Drydock
July 17, 2026 · View on GitHub
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.
How contributions work
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.
Where to help
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 many 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.
Getting started
-
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
Quick development loop
Backend (app/)
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 formatting
Frontend (ui/)
npm 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 formatting
Docker QA environment
docker build -t drydock:dev .
docker compose -f test/qa-compose.yml up -d # Starts on port 3333
You 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.
Architecture overview
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
├── triggers/providers/ # Send notifications / actions
├── 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_NOTIFICATION_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.
Code style
- 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
Commit convention
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.
Testing (optional for contributors)
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.
Pull requests
- 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.
Reporting bugs
Open a GitHub Issue with steps to reproduce.
Security vulnerabilities
Do not open a public issue. See SECURITY.md for responsible disclosure instructions.
License
By contributing, you agree that your contributions will be licensed under the GNU Affero General Public License v3.0.
Maintainer reference
Full quality pipeline (maintainers only)
Pre-push checks
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 | qlty-smells | Code smell advisory scan (non-blocking) | Advisory |
| 6 | scripts-test | Repository maintenance script tests | Fail |
| 7 | workflow-tests | GitHub Actions workflow invariant tests | Fail |
| 8 | typecheck-ui | Vue/TypeScript type checking | Fail |
| 9 | web-scripts-test | Marketing/docs site script tests when site files change | Fail |
| 10 | coverage | Sharded app+ui parallel vitest with 100% threshold | Fail |
| 11 | build | Sharded app+ui parallel tsc/vite (no tests) | Fail |
| 12 | docker-build | Optional Docker image build when DD_LOCAL_DOCKER=1 | Fail |
| 13 | zizmor | GitHub Actions security scanning when available | 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.
E2E Cucumber and Playwright browser tests are intentionally not part of the local pre-push hook; they run in CI on the same commit.
Coverage policy
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.
Mutation testing
Stryker runs monthly (.github/workflows/quality-mutation-monthly.yml), advisory only. Use it as a quality signal, not a score target.
Dependency & container scanning
Grype runs via .github/workflows/security-grype.yml: the dependency scan (every package-lock.json) on pull requests touching dependency/Dockerfile surfaces, plus a weekly cron and manual dispatch; the container-image scan runs on the scheduled/manual runs. Both fail on HIGH/CRITICAL and upload SARIF to the Security tab. SAST is CodeQL and new-dependency CVEs are flagged by dependency-review (both in ci-verify.yml); OpenSSF Scorecard runs in security-scorecard.yml.