Dev environment in vscode using containers
By ppcamp
| 12 minutes read | 2493 wordsIntroduction
Usually programmers need to have multiple environments, when its attributions depends on maintaining legacies programs.
The problem
If you need some library or resource that only exist in a specific version, you’ll need to work with it, and due to this, you’ll need to have multiple environments.
Some approaches
To achieve this, you can try multiples approaches, like use a virtual machine (a virtual box, for
example). Another good approach, is having virtual environments, however, this approach is not
generic, hence it depends on tools built for each language, for example, to nodejs you can use
nvm, to python, you can use venv. If you’re under a linux distribution, you can, on your own,
use the PATH
variable to use different versions.
Furthermore, many tools were created. One of them, was the asdf, which is just a tool to manage the program version locally, or globally.
VsCode
The vscode is an editor built by Microsoft. This editor is built upon the electron framework, and therefore, it is just like an browser listening to an specific port of your server/app.
Hence the vscode depends mainly of a server, they made some changes, and with those changes, by now, in the version 1.67.2, the vscode server can be run in a different computer.
One of the main point that make vscode so popular, it is your modularity and its easiness to use. And with so, the Microsoft team built some important extensions to complement this simple editor.
Some of those extensions, are to make it easier to connect with another servers, and doing so, you can have extensions running under those servers and dealing the heavy load, while your client computer just need to get the processed information and sending text to it.
Searching in the vscode Extensions:Marketplace, you can install the Remote Development extension, which will give you the “ability” to connect to others servers using different connections protocols.
Using the SSH-Targets, you can setup a server machine into gcloud, aws, or equivalent, and connect with them using the ssh config file.
Vscode - SSH
After setup the vscode server, you must to create a SSH file, like the example bellow.
The file bellow assumes that you already configured an ssh key and stored it in the server. You can see more here about how to create and store keys.
Host default
HostName 127.0.0.1
User someuser
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile D:/someFolder/private_key
IdentitiesOnly yes
LogLevel FATAL
Here you can read more about the ssh server setup.
Docker
Docker is well known as a good way to maintain multiples programs, without worrying about the platform which the program will be released. According to the docker documentation:
Docker is also used as base for other tools, like k8s/rancher.
Vscode - Containers
With this said, you can with some work, create your own docker image and run it inside a container. After this, you can connect using an ssh connection.
However, this approach will consume a lot of time to make the whole setup, and again, like said before, the vscode teams tries to make it easier for us. Therefore, the team created a “container” extension, which with some simple steps, allow you to have an isolated specific environment.
So, how to make this setup?
First of all, you’ll need the docker installed and running. Also, you’ll need to install the extensions.
To create a new environment, you can follow the video bellow:
A NodeJS setup environment
Folder structure:
.
├── .devcontainer
│ ├── devcontainer.json
│ └── Dockerfile
├── src
│ ├── build
│ │ └── index.js
│ ├── index.ts
│ ├── package.json
│ ├── package-lock.json
│ └── tsconfig.json
└── .vscode
├── extensions.json
└── launch.json
4 directories, 9 files
devcontainer.json
1// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node
3{
4 "name": "Node.js",
5 "build": {
6 "dockerfile": "Dockerfile",
7 // Update 'VARIANT' to pick a Node version: 18, 16, 14.
8 // Append -bullseye or -buster to pin to an OS version.
9 // Use -bullseye variants on local arm64/Apple Silicon.
10 "args": { "VARIANT": "18-buster" }
11 },
12
13 // Set *default* container specific settings.json values on container create.
14 "settings": {},
15
16 // Add the IDs of extensions you want installed when the container is created.
17 "extensions": [
18 "dbaeumer.vscode-eslint",
19 "ms-vscode.vscode-typescript-tslint-plugin",
20 "esbenp.prettier-vscode",
21 "VisualStudioExptTeam.vscodeintellicode",
22 "mhutchie.git-graph"
23 ],
24
25 // Use 'forwardPorts' to make a list of ports inside the container available locally.
26 // "forwardPorts": [],
27
28 // Use 'postCreateCommand' to run commands after the container is created.
29 // "postCreateCommand": "yarn install",
30
31 // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
32 "remoteUser": "node",
33 "features": {
34 "git": "latest"
35 }
36}
.devcontainer/Dockerfile
1# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node/.devcontainer/base.Dockerfile
2
3# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
4ARG VARIANT="18-bullseye"
5FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
6
7# [Optional] Uncomment this section to install additional OS packages.
8# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
9# && apt-get -y install --no-install-recommends <your-package-list-here>
10
11# [Optional] Uncomment if you want to install an additional version of node using nvm
12# ARG EXTRA_NODE_VERSION=10
13# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
14
15# [Optional] Uncomment if you want to install more global node modules
16# RUN su node -c "npm install -g <your-package-list-here>"
.vscode/extensions.json
1{
2 "recommendations": [
3 "ms-vscode-remote.remote-containers",
4 "ms-azuretools.vscode-docker",
5 "visualstudioexptteam.vscodeintellicode",
6 "esbenp.prettier-vscode",
7 "dbaeumer.vscode-eslint",
8 "ms-vscode.vscode-typescript-tslint-plugin",
9 "mhutchie.git-graph"
10 ]
11}
.vscode/launch.json
1{
2 // Use IntelliSense to learn about possible attributes.
3 // Hover to view descriptions of existing attributes.
4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 "version": "0.2.0",
6 "configurations": [
7 {
8 "type": "pwa-node",
9 "request": "launch",
10 "name": "Launch Program",
11 "skipFiles": [
12 "<node_internals>/**"
13 ],
14 "program": "${workspaceFolder}/src/index.js"
15 }
16 ]
17}
src/index.ts
1const a: number = 5;
2const b: number = 1;
3
4export function hello(who: string): string {return `Hello ${who}`;}
5
6console.log("Some code test ", a + b);
7console.log(hello("world"));
src/package.json
1{
2 "name": "src",
3 "version": "1.0.0",
4 "description": "Just a simple nodejs",
5 "main": "index.js",
6 "type": "commonjs",
7 "scripts": {
8 "test": "echo \"Error: no test specified\" && exit 1",
9 "build": "npx tsc",
10 "start": "node build/index.js"
11 },
12 "keywords": [
13 "vscode",
14 "container",
15 "environment"
16 ],
17 "author": "ppcamp",
18 "license": "ISC",
19 "devDependencies": {
20 "typescript": "^4.6.4"
21 }
22}
src/tsconfig.json
1{
2 "compilerOptions": {
3 /* Visit https://aka.ms/tsconfig.json to read more about this file */
4
5 /* Projects */
6 // "incremental": true, /* Enable incremental compilation */
7 // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12
13 /* Language and Environment */
14 "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 // "jsx": "preserve", /* Specify what JSX code is generated. */
17 // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25
26 /* Modules */
27 "module": "commonjs", /* Specify what module code is generated. */
28 // "rootDir": "./", /* Specify the root folder within your source files. */
29 // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 // "resolveJsonModule": true, /* Enable importing .json files */
37 // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
38
39 /* JavaScript Support */
40 // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43
44 /* Emit */
45 // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 "outDir": "./build", /* Specify an output folder for all emitted files. */
51 // "removeComments": true, /* Disable emitting comments. */
52 // "noEmit": true, /* Disable emitting files from a compilation. */
53 // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 // "newLine": "crlf", /* Set the newline character for emitting files. */
62 // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68
69 /* Interop Constraints */
70 // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75
76 /* Type Checking */
77 "strict": true, /* Enable all strict type-checking options. */
78 // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96
97 /* Completeness */
98 // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 }
101}
Conclusions
With all those points, we now have, not only, but a good way to handle with several isolated environments without worrying about the configurations, and without need some heavy tools. Since that each docker instance consumes less resources than the other tools.
Anyway, that was just a simple article to study the vscode container extensions and show the power of this beautiful editor.
Best regards, @ppcamp