Configuring TypeScript path aliases in a DevContainer
Path aliases such as @app/* keep imports readable, but they routinely break in three different layers: the editor resolves them, tsc does not emit a runtime rewrite, and node then throws Cannot find module '@app/...'. Inside a DevContainer this is worse because the working directory and the mounted workspace path must agree. This page wires aliases so they resolve identically in the editor, the compiler, and the runtime. It extends the Node.js and TypeScript Workspace Configuration cluster.
Prerequisites
- Node.js 20+ and TypeScript 5.4+ in the container
- A
tsconfig.jsonat the workspace root workspaceFolderaligned with your project root indevcontainer.json
How-To Steps
-
Declare the aliases in
tsconfig.jsonwithbaseUrlandpaths:{ "compilerOptions": { "baseUrl": ".", "paths": { "@app/*": ["src/*"] }, "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist" } }Verify: the editor stops underlining
import { x } from "@app/x". -
Resolve aliases at runtime —
tscdoes not rewrite them. For development, run throughtsx, which honourstsconfigpaths:{ "image": "mcr.microsoft.com/devcontainers/typescript-node:20@sha256:7c3e...", "postCreateCommand": "npm ci", "remoteUser": "vscode" }npx tsx src/index.ts # aliases resolve, no extra config -
For compiled output, add
tsc-aliasso emitted JS has real relative paths:npm i -D typescript tsc-alias npx tsc && npx tsc-alias node dist/index.js # @app/* now points at ./dist/* -
Keep the editor and CLI in sync by scoping the TypeScript SDK to the workspace version:
{ "customizations": { "vscode": { "settings": { "typescript.tsdk": "node_modules/typescript/lib" } } } }Verify: VS Code’s “TypeScript: Select Version” shows the workspace version, not the bundled one.
Common Pitfalls
| Symptom | Root Cause | Remediation |
|---|---|---|
Editor resolves, node throws | tsc does not rewrite alias paths | Use tsx in dev or tsc-alias for builds |
| Aliases work locally, not in container | baseUrl relative to a different working dir | Keep baseUrl: "." and align workspaceFolder |
| Jest can’t find aliases | Test runner ignores tsconfig paths | Add moduleNameMapper to the Jest config |
| Editor uses wrong TS version | Bundled VS Code TypeScript | Set typescript.tsdk to the workspace lib |
Conclusion
Aliases must be declared once and resolved in every layer that reads them — editor, compiler, runtime, and test runner. tsconfig covers the first two; tsx or tsc-alias covers the runtime, and moduleNameMapper covers tests. Miss one and the alias works “everywhere except” the place you forgot.
FAQ
Why do aliases work in VS Code but fail with node dist/index.js?
The editor reads tsconfig paths directly, but tsc emits them verbatim. Post-process with tsc-alias, or run a loader like tsx that resolves them at runtime.
Do I need tsc-alias if I use a bundler?
No. Bundlers (esbuild, Vite, Webpack) rewrite aliases during bundling. tsc-alias is only for plain tsc output run directly by Node.
Related
- Node.js and TypeScript Workspace Configuration — the parent cluster.
- Fixing Node.js npm cache in DevContainers — keeping
npm cifast inpostCreateCommand. - Using pnpm workspaces in a DevContainer — aliases across a monorepo.