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.json at the workspace root
  • workspaceFolder aligned with your project root in devcontainer.json

How-To Steps

  1. Declare the aliases in tsconfig.json with baseUrl and paths:

    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": { "@app/*": ["src/*"] },
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "outDir": "dist"
      }
    }
    

    Verify: the editor stops underlining import { x } from "@app/x".

  2. Resolve aliases at runtimetsc does not rewrite them. For development, run through tsx, which honours tsconfig paths:

    {
      "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
    
  3. For compiled output, add tsc-alias so 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/*
    
  4. 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

SymptomRoot CauseRemediation
Editor resolves, node throwstsc does not rewrite alias pathsUse tsx in dev or tsc-alias for builds
Aliases work locally, not in containerbaseUrl relative to a different working dirKeep baseUrl: "." and align workspaceFolder
Jest can’t find aliasesTest runner ignores tsconfig pathsAdd moduleNameMapper to the Jest config
Editor uses wrong TS versionBundled VS Code TypeScriptSet 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.