Why are people developing inside containers?

Why are people developing inside containers?

Nine years ago, in March 2013, Solomon Hykes and his cofounders revolutionized how we do software development with an open source platform called Docker. Although the creators of Docker didn't invent containers, they popularized them. Thanks to Docker, engineers can create tools, like GitHub Codespaces, that enable us to code in a development container hosted in the cloud.

I’ll admit that when I first heard about development containers, I had two questions:

  • Why are people developing inside containers?
  • What are containers?

If you have similar questions, this post is for you.

In this blog post, I'll explain what containers are, how they benefit engineers, and how to setup devcontainers in GitHub Codespaces.

What does it mean to develop inside a container?

Raise your hand if you’ve ever uttered the words, "It works on my machine." (Don’t worry; you're not alone)!

If you're not familiar with this phrase, it's a meme, but it's also a real phrase developers find themselves saying if they work in a codebase where the environments vary. Although working in varying environments is not ideal, it does happen. From my experience, situations like this occur when my local environment, my coworkers' local environments, staging, and production all have slight differences. Because the environments had slightly different configurations, bugs existed in one environment but didn’t exist in the other environment. It can feel so embarrassing and frustrating to build a feature or fix a bug that works locally but doesn't work in production or staging.

However, containers solve the issue of inconsistent developer environments. Containers enable software engineers to program in a consistent environment. Now, your coding environment can mirror production by using the same operating system, configurations, and dependencies. This ensures bugs and features behave the same across all environments relieving developers from the embarrassment of saying, "It works on my machine."

Now that we understand the purpose of containers, let’s explore how Codespaces leverages containers.

GitHub Codespaces takes software and cloud development to the next level

GitHub Codespaces allows you to code in a container hosted in the cloud. In this context, the cloud is an environment that doesn't reside on your computer but rather on the internet.

Faster onboarding

Typically, software engineers are responsible for setting up their local environment when they join a team. Local environment setup includes installing the required dependencies, linters, environment variables, and more. Developers may spend up to a week configuring their environment, depending on the quality of the documentation. When I was purely a software engineer, it took me about 2 days to set up my environment, and the experience was painful because I wanted to start coding immediately. Instead, I had to seed my database and edit my .zshrc file.

Fortunately, organizations can automate the onboarding process using GitHub Codespaces to configure a custom environment. When a new engineer joins the team, they can open a codespace and skip the local environment setup because the required extensions, dependencies, and environment variables exist within the codespace.

Code from anywhere

With Codespaces, I can code anywhere that I have internet access. If I switch devices or forget my laptop at home, I can easily resume my work on an iPad while I’m on the plane without cloning a repository, downloading my preferred IDE, and setting up a local environment. This is possible because Codespaces opens a Visual Studio Code-like editor inside the browser. The best part is Codespaces can autosave my code even if I forget to push my changes to my repository.

Consistent environments

As mentioned in the paragraphs above, containers allow you to work in a mirrored production environment. Because GitHub Codespaces uses containers, you can get the same results and developer experience in your local environment as you would in your production environment.

Additionally, sometimes when changes happen to the codebase, such as infrastructure enhancements, local environments can break. When local environments break, it’s up to the developer to restore their developer environment. However, GitHub Codespaces use of containers brings uniformity to developer environments and reduces the chances of working in a broken environment.

Three files you may need to configure a Codespace

You can leverage three files to make the Codespaces experience works for you and your teammates: the devcontainer.json file, the Dockerfile, and the docker-compose.yml file. Each of these files lives in the .devcontainer directory at the root of your repository.

The devcontainer.json file

The devcontainer.json file is a configuration file that tells GitHub Codespaces how to configure a codespace. Inside a devcontainer file, you can configure the following:

  • Extensions
  • Environment variables
  • Dockerfile
  • Port forwarding
  • Post-creation commands
  • And more

    This means that whenever you or someone opens a codepspace, the extensions, environment variables, and other configurations you specify in the devcontainer.json file will automatically install when they open a codespace in the specified repository. For example, if I wanted folks to have the same linter and extensions as me, I could add the following to my devcontainer.json file: devcontainer.json

    {
      "name": "Node.js",
      "build": {
          "dockerfile": "Dockerfile",
          // Update 'VARIANT' to pick a Node version: 18, 16, 14.
          // Append -bullseye or -buster to pin to an OS version.
          // Use -bullseye variants on local arm64/Apple Silicon.
          "args": { "VARIANT": "16-bullseye" }
      },
    
      // Configure tool-specific properties.
      "customizations": {
          // Configure properties specific to VS Code.
          "vscode": {
              // Add the IDs of extensions you want installed when the container is created.
              "extensions": [
                  "dbaeumer.vscode-eslint", // this is the exentension id for eslint
                  "esbenp.prettier-vscode", // this is the extension id for prettier
                  "ms-vsliveshare.vsliveshare", // this is the extension id for live share
              ]
          }
      },
    
      // Use 'forwardPorts' to make a list of ports inside the container available locally.
      // "forwardPorts": [],
    
      // Use 'postCreateCommand' to run commands after the container is created.
      // "postCreateCommand": "yarn install",
    
      // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
      "remoteUser": "node"
    }
    

    You can learn more about the devcontainer.json file here.

    The Dockerfile

    The Dockerfile is a configuration file that tells GitHub Codespaces how to build a container. It contains a list of commands that the Docker client calls while creating an image. Dockerfiles are used to automate the installation and configuration of a container. For example, if I wanted to install Node.js in a container, I could add the following to my Dockerfile: Dockerfile

    FROM node:16-bullseye
    

    You can learn more about Dockerfiles here, and you can learn more about setting up a node.js in Codespaces here.

The docker-compose.yml file

You don't need a docker-compose.yml file in a Codespace, but it's useful if you want to run multiple containers. For example, if you want to run a database and a web server in a Codespace, you can use the docker-compose.yml file to run both containers. You can learn more about docker-compose.yml files here. Here's an example of what a docker-compose.yml file that's connecting to a database might look like: docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile
      args:
        VARIANT: "3"
        NODE_VERSION: "none"

    volumes:
      - ..:/workspace:cached

    command: sleep infinity

    network_mode: service:db

  db:
    image: postgres:latest
    restart: unless-stopped
    volumes:
      - postgres-data:/var/lib/postgresql/data
    hostname: postgres
    environment:
      POSTGRES_DB: my_media
      POSTGRES_USER: example
      POSTGRES_PASSWORD: pass
      POSTGRES_HOST_AUTH_METHOD: trust
    ports:
      - 5432:5432

volumes:
  postgres-data: null

Codespaces is not the same as GitHub’s web editor

GitHub Codespaces is not the same as GitHub’s web editor. The web editor is the editor that appears when you press "." in a repository. It is a lightweight editor that allows you to edit files in your repository. The web editor is great for making minor changes to a file, but it’s not ideal for writing code. However, Codespaces allows you to run a full-fledged IDE in the browser equipped with a terminal and more.

P.S. I wrote this blog post with GitHub Copilot. 😉 (These are all my own words, thoughts, and frustrations, but Copilot helped unblock me when I couldn’t find the right phrases or motivation.)

There's so much to learn about GitHub Codespaces. Comment below if you have any topics regarding GitHub Codespaces you'd like me to cover in future posts. Follow me and GitHub on DEV if you want to see more content like this. Thanks for reading! 🙏🏿

Did you find this article valuable?

Support Rizel Scarlett by becoming a sponsor. Any amount is appreciated!