Cloud Native 21 min read

Engineering Evolution of Tencent Docs Microservice Gateway: Monorepo, pnpm, and Docker Optimizations

Tencent Docs transformed its gateway from a monolithic Node.js service into a four‑service micro‑architecture using a pnpm monorepo, deterministic lockfiles, and a custom Docker context script that eliminates symlinks, pins @grpc/grpc‑js, and produces smaller, reproducible images.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Engineering Evolution of Tencent Docs Microservice Gateway: Monorepo, pnpm, and Docker Optimizations

Tencent Docs gateway serves as the traffic entry for the document front‑end and has evolved from a monolithic service to a microservice architecture managed in a Node.js monorepo.

Existing issues include uncontrolled dependency upgrades (e.g., @grpc/grpc-js >1.8.x causing excessive CPU usage), lack of lock‑file usage, and complex Docker image building due to soft/hard links in the workspace.

Engineering analysis identifies four microservices maintained in a single repository, with Yarn + Lerna + npm workspace previously used, leading to dependency promotion problems.

Key considerations:

Need to lock all transitive dependencies.

Docker images must contain a clean node_modules without symlinks.

Solution adopts pnpm workspace, which provides deterministic dependency resolution and hooks (readPackage, afterAllResolved) to enforce specific versions (e.g., pin @grpc/grpc-js to 1.7.3).

Custom Docker context generation script ( .pnpm-context.mjs ) extracts only the required packages, dependencies, and meta files for each service, producing a tar stream with three directories: meta , deps , pkg .

Dockerfile uses multi‑stage build and the --mount=type=cache option, with package-import-method=copy to avoid symlinks.

Soft links are leveraged to keep original file paths while copying files, reducing validation effort.

The .pnpmfile.mjs defines hooks:

// readPackage hook
function readPackage(pkg, context) {
  if (pkg && pkg.dependencies && pkg.dependencies['@grpc/grpc-js']) {
    const grpcVersion = pkg.dependencies['@grpc/grpc-js'];
    if (compareVersion(grpcVersion, '1.7.3') >= 1) {
      pkg.dependencies['@grpc/grpc-js'] = '1.7.3';
      context.log(`Modifying the @grpc/grpc-js package...`);
    }
  }
  return pkg;
}

// afterAllResolved hook
function afterAllResolved(lockfile, context) {
  // ... lock @grpc/grpc-js to 1.7.3 across all packages
  return lockfile;
}
module.exports = { hooks: { readPackage, afterAllResolved } };

Results: all dependencies are fully locked, ghost dependencies eliminated, Docker images are smaller and deterministic, and the build pipeline is simplified.

Conclusion emphasizes the importance of continuous engineering improvement for backend services.

dockerMicroservicesMonorepoNode.jsdependency managementpnpm
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.