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:

  1. You write the code — focus on the feature or fix itself
  2. Open a PR — even if it's rough, incomplete, or has no tests
  3. 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 issue labels when they exist, but don't wait for them.

Getting started

  1. Fork the repository and clone your fork

  2. Use Node.js 24+:

    nvm use || nvm install
    
  3. Install dependencies — each workspace manages its own:

    cd app && npm install
    cd ../ui && npm install
    
  4. Create a branch from the appropriate base:

    • Bug fixes: branch from main
    • New features: branch from the active feature branch (check open branches)

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, NodeNext module resolution)
  • Linter/formatter: Biome — run npm run lint:fix to auto-fix
  • Line width: 100, single quotes
  • No transpiler: compiles with tsc directly

Commit convention

We use Gitmoji + Conventional Commits:

<emoji> <type>(<scope>): <description>
EmojiTypeUse
featNew feature
🐛fixBug fix
📝docsDocumentation
💄styleUI/cosmetic changes
♻️refactorCode refactor (no feature/fix)
perfPerformance improvement
testAdding/updating tests
🔧choreBuild, config, tooling
🔒securitySecurity fix
⬆️depsDependency upgrade
🗑️revertIntentional 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, or vi

  • 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. Use vi.hoisted() for values needed in mock factories.

Pull requests

  • Target: main for 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:

PriorityStepWhat it doesOn Failure
1clean-treeRejects uncommitted changesFail
2ts-nocheckChecks for @ts-nocheck directivesFail
3biome checkLinting and formattingFail
4qltyStatic analysis (medium+ severity gate)Fail
5qlty-smellsCode smell advisory scan (non-blocking)Advisory
6scripts-testRepository maintenance script testsFail
7workflow-testsGitHub Actions workflow invariant testsFail
8typecheck-uiVue/TypeScript type checkingFail
9web-scripts-testMarketing/docs site script tests when site files changeFail
10coverageSharded app+ui parallel vitest with 100% thresholdFail
11buildSharded app+ui parallel tsc/vite (no tests)Fail
12docker-buildOptional Docker image build when DD_LOCAL_DOCKER=1Fail
13zizmorGitHub Actions security scanning when availableFail

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.