One of the hardest things for developers is to start a project that is already underway.

It often happens that the requirements for a new project force us to modify the versions of the SDKs and the tools that we have installed locally to debug the environment, which makes us lose days until everything works as expected.

If we consider versions that are in a beta or preview phase, things get further complicated. To prevent this from happening, I would like to introduce you to GitHub Codespaces, which not only allows you to generate the development environment that your application needs within a container but also makes it possible to develop anywhere and from any device.

Let’s Begin!

What is GitHub Codespaces?

Github codespaces is a cloud environment that is accessible from a browser and has all the features necessary for developing dedicated code.

You may have seen the GitHub announcement, every time you are in a repo and hit the period “key.” A web editor will open with the repository in question.

However, you will not be able to run the terminal since it is simply a web editor and there isn’t a backend machine. Sometimes this can be more than enough, but imagine that you need to run an environment or compile a project but you do not have a system that meets the needs (an iPad, a Surface Go, etc.) at hand.

GitHub Codespaces solves this problem by allowing you to run the IDE in the cloud to work as if you were using a local system and that too with your specified configuration.

Note: This feature is only available on Team and Enterprise accounts.

Sample code

For this example I will use a very simple Node.js application:

const express = require('express');
const app = express();
const port = 8080;
app.use(express.static('public'));
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
})
app.listen(port, () => {
    console.log(`Example app listening at http://localhost:${port}`);
})

The above code has the following structure:

Structure of the code

You can launch this project locally without any problem, as long as you have installed Node.js, git, and the VS Code extensions that are useful for a project in this language. You may also want to use tools like Nodemon, which will refresh the application automatically.

The problem comes when you not only work with this configuration but make use of many others such as development with .NET Core, Python, Docker, or Kubernetes and that makes you install more tools and extensions that you don’t always need, and they can end up hurting the performance of your development environment.

The problem isn’t limited to your own performance. If someone else joins the team and needs to get their system “ready” with the tools and frameworks that are needed to start the application then they will also lose several hours which will hurt the overall team performance (it’s going to be worse if you are working using different operating systems).

Working with Remote Containers

Visual Studio Code offers an extension called Remote Containers, or Dev Containers, which allows you to generate the development environment that your application needs within a container. In this way, all the developers in your team who are working on a project will have the same development environment automatically.

This will make the experience of starting your project as agile as possible and also if you are the one who has defined the environment, you can separate multiple work environments per project with different configurations on the same machine. Cool huh?

How do I start?

To use this feature, you must have Docker installed on your machine since the containers will run on it. It is also necessary to install the Remote Containers extension, which will help us build the development environment.

The first thing you need to do is create a folder called .devcontainer in the root directory. This is the one that the extension will try to identify in order to know if it has a configuration to mount a container with the indications included in it.

In it, you must have at least one file called devcontainer.json which is the one that will have the environment configuration.

For this example I have used the following:

{
    "name": "dev-demo",
    "dockerFile": "Dockerfile",
    "forwardPorts": [
        8080
    ],
    "settings": {
        "workbench.colorTheme": "Visual Studio Light"
    },    
    "containerEnv": {
        "NODE_ENV": "development",
    },
    "extensions": [
        "coenraads.bracket-pair-colorizer-2",
        "eg2.vscode-npm-script",
        "christian-kohler.npm-intellisense",
        "dbaeumer.vscode-eslint",
        "streetsidesoftware.code-spell-checker",
        "formulahendry.auto-close-tag",
        "mikestead.dotenv",
        "christian-kohler.path-intellisense",
        "davidanson.vscode-markdownlint",
        "pkief.material-icon-theme",
        "humao.rest-client"
    ],
	"postCreateCommand": "/bin/bash -c .devcontainer/post-create.sh",
    "postAttachCommand": "nodemon server.js"
}

As you can see, it is a JSON with different sections:

  • name: it is the name that the dev container will have. Ideally, this should be a descriptive name.
  • dockerFile: it is used to indicate the name of the Dockerfile that we are going to use to create the environment. In my case, in the same .devcontainer folder, I have one like the following:
FROM node:12.16.1-alpine
WORKDIR /code
RUN apk update && apk upgrade 
    && apk add git bash curl 
    && npm install -g nodemon
COPY . .

In this example, I am using the node: 12.16.1-alpine image simply to demonstrate that the configuration I have within this development environment is different from the one I have locally, where I am using version 14 of Node.js. On the other hand, I install some utilities like git, bash, and cURL, in addition to the Nodemon module that I will use during development.

  • fordwardPorts: these are the ports that my application uses so that I can access them from the local machine and so debugging is easy.
  • settings: for this example, and when you have multiple environments it is super useful, I have modified the Visual Studio Code theme so that it is evident that I am not in the local environment in that instance of the IDE. I usually use the dark version and here I have set the light version.
  • containerEnv: I have also added an environment variable, which is usually super useful when developing.
  • postCreateCommand : I have added this property so you can see that it is possible to execute commands or scripts during the life cycle of the dev container. In this case, this script will be launched when the creation is finished. The content of this is:
    #!/bin/bash
    # this runs in background after UI is available
    #Remove node_modules folder
    echo "Remove node_modules folder first"
    rm -rf node_modules
    #Install npm dependencies
    echo "Install dependencies"
    npm install
  • postAttachCommand: Finally I have added the nodemon server.js command to this property so that every time you hook to the container it will by default lift the application.

There are many more options to add in the devcontainer.json file, this is just a very small example of what you can do.

Now that you know everything that is in this folder and how the defined configuration works, how do we launch this?

The easiest way is through this button here:

blank

Select the Reopen in Container option and the process will begin creating the container and your custom IDE. The result should look like the following:

blank

In the end, the result, in this case, will be a Visual Studio Code window with the Light theme, with a specific Node.js version, several extensions chosen to develop more comfortably, and my application already running and exposed by port 8080.

And the best part is: this configuration is versioned in the project repo so that any member can use it. Simply spectacular!

Now that we have seen all this, and we have understood all the steps, delete the .devcontainer folder, select the icon at the bottom left again, select Reopen in Container again and you will see that this time it lets you choose between several pre-built configurations.

You do not need to start your configuration from scratch, and also you already know where you have to touch to finish giving it your nerd touch.

Back to Github Spaces

Now we will use the same project that we used in Visual Studio Remote Containers, but this time with GitHub. Once this is done in the Code section you will have a new option called Codespaces:
blank
Here you can  select the type of machine that you need to work with this specific repository:

blank
Choose the size of the machine

Once chosen, the creation process will begin. It will use the configuration of the .devcontainer folder, the one that we created for Remote Containers, to configure the environment.
blank
Once the process is finished you will see that a Visual Studio Code will appear in web format with the application running, in the same way, that we did on our local system.
blank

Final Words 👩‍💻

As you can see in the above example, Github Codespaces using Remote Containers is an excellent solution to our problem of replicating our configuration and development environment and they are also a great way to improve the performance of our team.