Remote Work Tools

Outdated dependencies are a compounding problem. Each week you ignore them, the upgrade diff grows and the risk of breaking changes increases. For remote teams, there’s no “Friday afternoon let’s update dependencies” session — you need a system that proposes updates automatically and makes merging them low-friction.


Renovate (Most Powerful, Self-Hostable)

Renovate is the most capable automated dependency updater. It groups updates, understands monorepos, respects your merge schedule, and auto-merges low-risk updates.

Option A: GitHub App (easiest)

  1. Install the Renovate GitHub App
  2. Add renovate.json to your repo root:
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:base"],
  "timezone": "America/New_York",
  "schedule": ["after 9am and before 5pm every weekday"],
  "prHourlyLimit": 2,
  "prConcurrentLimit": 10,
  "labels": ["dependencies"],
  "assignees": ["alice", "bob"],
  "packageRules": [
    {
      "matchUpdateTypes": ["minor", "patch"],
      "matchCurrentVersion": "!/^0/",
      "automerge": true,
      "automergeType": "pr",
      "platformAutomerge": true
    },
    {
      "matchPackageNames": ["react", "react-dom"],
      "groupName": "React packages",
      "schedule": ["on the first day of the month"]
    },
    {
      "matchPackagePatterns": ["^@types/"],
      "groupName": "TypeScript type definitions",
      "automerge": true
    },
    {
      "matchManagers": ["terraform"],
      "groupName": "Terraform providers",
      "schedule": ["on the first day of the month"]
    }
  ],
  "vulnerabilityAlerts": {
    "labels": ["security"],
    "assignees": ["security-team"],
    "schedule": ["at any time"]
  }
}

Option B: Self-hosted with Docker

# docker-compose.yml (for your Renovate runner server)
version: "3.8"
services:
  renovate:
    image: renovate/renovate:latest
    volumes:
      - /tmp/renovate:/tmp/renovate
    environment:
      - RENOVATE_TOKEN=${GITHUB_TOKEN}
      - RENOVATE_PLATFORM=github
      - LOG_LEVEL=info
    command: >
      --autodiscover=true
      --autodiscover-filter=your-org/*

Run on a schedule:

# crontab
0 8 * * 1-5 docker run --rm \
  -e RENOVATE_TOKEN=$GITHUB_TOKEN \
  renovate/renovate:latest \
  --autodiscover=true \
  --autodiscover-filter=your-org/*

Renovate for Monorepos

When your team uses a monorepo, Renovate handles multiple package managers in a single run. Set enabledManagers to keep things explicit:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:base"],
  "enabledManagers": ["npm", "docker", "github-actions", "terraform"],
  "ignorePaths": ["**/node_modules/**", "**/fixtures/**"],
  "packageRules": [
    {
      "matchPaths": ["services/api/**"],
      "groupName": "API service dependencies",
      "assignees": ["backend-team"]
    },
    {
      "matchPaths": ["services/frontend/**"],
      "groupName": "Frontend dependencies",
      "assignees": ["frontend-team"]
    }
  ]
}

This ensures PRs land in front of the right team without everyone getting notified for changes outside their domain.


Dependabot (GitHub Native)

Dependabot is built into GitHub, requires no infrastructure, and is sufficient for most teams. It’s less flexible than Renovate but has zero setup friction.

# .github/dependabot.yml
version: 2
updates:
  # npm dependencies
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "09:00"
      timezone: "America/New_York"
    labels:
      - "dependencies"
      - "javascript"
    reviewers:
      - "frontend-team"
    open-pull-requests-limit: 10
    groups:
      react:
        patterns:
          - "react"
          - "react-dom"
          - "@types/react*"
      testing:
        patterns:
          - "jest*"
          - "@testing-library/*"
          - "vitest*"

  # Go modules
  - package-ecosystem: "gomod"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "tuesday"
    labels:
      - "dependencies"
      - "go"
    reviewers:
      - "backend-team"

  # Docker base images
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "monthly"
    labels:
      - "dependencies"
      - "docker"

  # Terraform providers
  - package-ecosystem: "terraform"
    directory: "/infra/terraform"
    schedule:
      interval: "monthly"
    labels:
      - "dependencies"
      - "infrastructure"

  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "ci"

Auto-merge Dependabot patch updates via GitHub Actions:

# .github/workflows/dependabot-automerge.yml
name: Dependabot Auto-merge
on: pull_request

permissions:
  contents: write
  pull-requests: write

jobs:
  automerge:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'
    steps:
      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2

      - name: Auto-merge patch and minor updates
        if: |
          steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
          steps.metadata.outputs.update-type == 'version-update:semver-minor'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

OWASP Dependency-Check (Vulnerability Scanning)

Renovate and Dependabot handle version updates. OWASP Dependency-Check scans for known vulnerabilities in your current dependencies, even if you’re up-to-date:

# .github/workflows/dependency-check.yml
name: Dependency Vulnerability Scan
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 8 * * 1'  # Weekly

jobs:
  dependency-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'your-project'
          path: '.'
          format: 'HTML'
          args: >
            --enableRetired
            --failOnCVSS 7
            --suppression suppression.xml

      - name: Upload results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: dependency-check-report
          path: ${{ github.workspace }}/reports

Create suppression.xml to suppress false positives:

<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
  <suppress>
    <notes>False positive - this CVE affects a different component</notes>
    <cve>CVE-2023-12345</cve>
  </suppress>
</suppressions>

Running OWASP Dependency-Check Locally

For local scans before pushing, run the CLI directly:

# Download and run the CLI scanner
VERSION="9.0.9"
curl -sL "https://github.com/jeremylong/DependencyCheck/releases/download/v${VERSION}/dependency-check-${VERSION}-release.zip" -o dc.zip
unzip dc.zip -d /opt/dependency-check

# Scan a Node project
/opt/dependency-check/bin/dependency-check.sh \
  --project "myapp" \
  --scan ./node_modules \
  --format HTML \
  --out ./reports \
  --failOnCVSS 7

# Update the NVD database cache (do this weekly)
/opt/dependency-check/bin/dependency-check.sh --updateonly

The NVD database download takes several minutes the first time. After that, incremental updates are fast. Cache it on your CI runner to avoid re-downloading on every job.


Snyk (Security-First)

Snyk combines vulnerability detection with automated fix PRs and license compliance checking:

# Install CLI
npm install -g snyk

# Authenticate
snyk auth

# Scan project
snyk test --severity-threshold=high

# Watch project for new vulnerabilities (CI)
snyk monitor

# Fix vulnerabilities automatically
snyk fix

Add to CI:

# .github/workflows/snyk.yml
name: Snyk Security Scan
on: [push, pull_request]
jobs:
  snyk:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --fail-on=upgradable

Snyk for Container Images

Snyk also scans Docker images for OS-level CVEs, which OWASP Dependency-Check misses:

# Scan a container image
snyk container test your-org/your-app:latest \
  --file=Dockerfile \
  --severity-threshold=high

# Monitor an image in the Snyk dashboard
snyk container monitor your-org/your-app:latest \
  --project-name="production-api"

Add container scanning to your CI pipeline after the image build step:

- name: Scan container image
  uses: snyk/actions/docker@master
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
  with:
    image: your-org/your-app:${{ github.sha }}
    args: --severity-threshold=high

License Compliance

Track open source license compliance before it becomes a legal issue:

# Node.js
npm install -g license-checker
license-checker --summary --excludePrivatePackages

# Output licenses in CSV for legal review
license-checker --csv --out licenses.csv

# Fail on GPL licenses in commercial projects
license-checker \
  --failOn "GPL-2.0;GPL-3.0;AGPL-3.0" \
  --excludePrivatePackages
# Python
pip install pip-licenses
pip-licenses --format=markdown --with-urls

# Go
go install github.com/google/go-licenses@latest
go-licenses check ./... --disallowed_types=restricted,forbidden

Automating License Reports in CI

Generate a license report on every PR so legal review can happen before merge rather than after:

# .github/workflows/license-check.yml
name: License Compliance
on: [pull_request]

jobs:
  licenses:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Generate license report
        run: |
          npm install -g license-checker
          license-checker \
            --failOn "GPL-2.0;GPL-3.0;AGPL-3.0;LGPL-2.0;LGPL-3.0" \
            --excludePrivatePackages \
            --csv \
            --out licenses.csv

      - name: Upload license report
        uses: actions/upload-artifact@v4
        with:
          name: license-report
          path: licenses.csv

Keep a license-allowlist.txt in your repo with approved licenses. Run license-checker against the allowlist on every merge to main:

# Strict allowlist approach
license-checker \
  --onlyAllow "MIT;ISC;BSD-2-Clause;BSD-3-Clause;Apache-2.0;0BSD;CC0-1.0" \
  --excludePrivatePackages

Tool Comparison

Tool Type Self-Hosted Best For
Renovate Update automation Yes Fine-grained control, monorepos
Dependabot Update automation No GitHub-native, quick setup
OWASP DC Vulnerability scan Yes CVE scanning, offline
Snyk Security + fixes No Security-first teams

Use Renovate or Dependabot for updates, OWASP or Snyk for vulnerability scanning — they complement each other.

Dependency Update Workflow for Async Remote Teams

The biggest failure mode for remote teams isn’t tooling — it’s process. PRs opened by Renovate or Dependabot sit unreviewed for days because no one owns them. Fix this with explicit ownership rules.

Set a standing agenda item in your weekly async update (Loom, Notion, or Slack thread) for dependency PR status. The assigned reviewer for the week checks pending dependency PRs each Monday and either merges, comments with a block reason, or escalates to the full team. This simple rotation prevents dependency debt from silently accumulating.

Use branch protection rules to enforce that dependency PRs pass CI before merge:

# .github/branch-protection.json (configure via gh CLI or Terraform)
{
  "required_status_checks": {
    "strict": true,
    "contexts": [
      "test",
      "lint",
      "security-scan"
    ]
  },
  "required_pull_request_reviews": {
    "dismiss_stale_reviews": true,
    "required_approving_review_count": 1
  }
}

For dependency PRs specifically, one approval is usually sufficient — the CI pipeline is the real gatekeeper. Reserve two-approval requirements for application code changes.


Tracking Dependency Health Over Time

Point-in-time scans don’t tell you whether your dependency posture is improving or degrading. Add a weekly dependency health report to your team dashboard:

#!/bin/bash
# dependency-health.sh — outputs a summary suitable for Slack or Notion
echo "=== Dependency Health Report $(date +%Y-%m-%d) ==="

# Count open Dependabot/Renovate PRs
echo "Open dependency PRs:"
gh pr list \
  --label dependencies \
  --state open \
  --json number,title,createdAt \
  --jq '.[] | "\(.number): \(.title) (opened \(.createdAt[:10]))"'

echo ""
echo "PRs older than 14 days:"
gh pr list \
  --label dependencies \
  --state open \
  --json number,title,createdAt \
  --jq '.[] | select((.createdAt | fromdateiso8601) < (now - 1209600)) | "\(.number): \(.title)"'

Post this report to a #dependency-updates Slack channel every Monday. The act of making the backlog visible reduces it — teams that track open dependency PRs consistently close them faster than teams that rely on email notifications alone.


Choosing the Right Stack for Your Team Size

For a team of 1–5 engineers, Dependabot plus Snyk free tier covers the essentials with zero infrastructure overhead. Add license-checker to CI and you have full coverage.

For teams of 6–20 engineers working across multiple repos or a monorepo, Renovate’s grouping and scheduling control saves significant review time. Self-host it on a small VM or run it via GitHub Actions on a cron schedule.

For teams of 20+ or those in regulated industries (fintech, healthcare), add OWASP Dependency-Check for offline CVE scanning and generate formal license reports quarterly using go-licenses or pip-licenses depending on your stack. Snyk’s paid tier adds policy enforcement, so security requirements can be defined once and applied across all repos automatically.



Built by theluckystrike — More at zovo.one