# vscode-devcontainer-tutorial **Repository Path**: fgf11/vscode-devcontainer-tutorial ## Basic Information - **Project Name**: vscode-devcontainer-tutorial - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-08 - **Last Updated**: 2026-04-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Visual Studio Code Dev Container Tutorial ## Intro > The **Visual Studio Code Remote - Containers** extension lets you use > a Docker container as a full-featured development environment. It is a hassle to install correct versions of software tools and keep them updated during your development. By combining **Docker** and **VSCode**, you can drastically improve your development experience. In this tutorial, you will learn how to set up a development environment which includes an `express.js` server and a `PostgreSQL` database instance. You can use this project as a template and add more Docker containers to your environment if needed. Here are a few Docker images that are available on [Docker Hub](https://hub.docker.com/). It is up to your creativity how you will combine these containers to make something awesome. - Web Server - [Apache Web Server](https://hub.docker.com/_/httpd) - [Nginx](https://hub.docker.com/_/nginx) - Database - [MySQL](https://hub.docker.com/_/mysql) - [PostgreSQL](https://hub.docker.com/_/postgres) - [MongoDB](https://hub.docker.com/_/mongo) - [Redis](https://hub.docker.com/_/redis) - [Memcached](https://hub.docker.com/_/memcached) - [Couchbase](https://hub.docker.com/_/couchbase) - Language / Framework - [Node](https://hub.docker.com/_/node) - [Python](https://hub.docker.com/_/python) - [Java](https://hub.docker.com/_/openjdk) - [Php](https://hub.docker.com/_/php) - [Go](https://hub.docker.com/_/golang) - [Rust](https://hub.docker.com/_/rust) --- ## Requisites Before you jump into this tutorial, you will need **Docker**, **Visual Studio Code**, and **Remote - Containers** VSCode extension installed on your machine. You can find instructions for installation here: - Docker: - Visual Studio Code: - Remote - Containers: --- ## Let's Begin For dev containers to work, you need to write three configuration files: - `.devcontainer/Dockerfile` - `.devcontainer/docker-compose.yml` - `.devcontainer/devcontainer.json` Let's look at each files and try to understand what they do. --- ## Dockerfile `Dockerfile` is a recipe for creating a docker image. The docker image created from `Dockerfile` is then used to create docker containers. For now, you do not need to understand the difference between `Dockerfile`, docker images and docker containers. Just know that a docker container is functionally very similar to a virtual machine, but docker containers are easier to create/destroy/start/stop. If you need more thorough explanation, refer to Docker's official documentation. Here's the first line of `Dockerfile`: ```docker FROM ubuntu:20.04 ``` `Dockerfile` is a blueprint for a docker image, and you can use `FROM` command to select which image to use as a template for your docker image. In this case, it is `ubuntu:20.04`. Next, we declare variables to be used in this `Dockerfile`: ```docker ARG USERNAME=vscode ARG USER_UID=1000 ARG USER_GID=$USER_UID ``` Because default user of `ubuntu:20.04` is the root user, we will later create a new system user using these variables. ```docker ENV DEBIAN_FRONTEND=noninteractive ``` Debian and Ubuntu use a package manager called `apt`, and `apt` can prompt user for things like file permissions and timezone selection. We need to disable `apt` from prompting, in order to fully automate docker image building. The environment variable `DEBIAN_FRONTEND` controls the behavior of `apt`. Next, we will install our dependencies to the docker image using `apt` package manager. ```docker RUN apt-get -y update --no-install-recommends \ && apt-get -y install --no-install-recommends \ build-essential \ curl \ ca-certificates \ apt-utils \ dialog \ git \ vim \ && apt-get autoremove -y \ && apt-get clean -y ``` If you need more packages, just add the required package names to the list. Now we have `curl` and `ca-certificates` installed, and we are able to download setup script for **Node.js**. We did not install `nodejs` package alongside with other packages, because Ubuntu's package repository only has an older version of Node. By downloading & executing setup script from __nodesource.com__, we can install version 12 of Node. ```docker RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - ``` After executing __nodesource.com__ setup script, we can now install version 12 of Node using `apt`. ```docker RUN apt-get -y update --no-install-recommends \ && apt-get -y install --no-install-recommends nodejs \ && apt-get autoremove -y \ && apt-get clean -y ``` Remember the variables we declared in the beginning? We create a system user using those variables. ```docker RUN groupadd --gid $USER_GID $USERNAME \ && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME ``` **NOTE**: If you want to use `sudo` command, you need to do some extra configuration. We will not cover the topic in this tutorial. We reset `DEBIAN_FRONTEND` variable to `dialog`, so we can use `apt` in interactive mode when we are actually using docker container. ```docker ENV DEBIAN_FRONTEND=dialog ``` Set the newly created system user as default, instead of root. ```docker USER $USERNAME ``` That's the end of `Dockerfile`. --- ## docker-compose.yml Now let's look at `docker-compose.yml` file. YAML is a popular file format for config files. It allows organizing complicated settings into concise key-value pairs. If you want to learn more about YAML, check out their official website. `docker-compose` is a command-line tool to manage multiple docker containers at the same time. Each container managed by `docker-compose` is called a "service", and we will use two services (express server + postgres server) in this tutorial. `docker-compose.yml` begins with a version number: ```yaml version: '3.7' ``` This is a compose file version number. Compose file version 3.7 only supports docker engine version 19.03 and higher, so you need to make sure that the docker installed on your system can handle this compose file. You can find version compatibility table for compose file here: YAML provides a feature called **anchor**. It allows you to write repeating part of a YAML file once as an **anchor**, then reference the anchor throughout the rest of the file as **aliases**. Our `docker-compose.yml` file begins with a top-level key `x-environment`. The ampersand means it is an anchor. ```yaml x-environment: &default-environment POSTGRES_USER: vscode POSTGRES_PASSWORD: notsecure POSTGRES_DB: tutorial ``` We will later refernce this anchor as aliases. After `x-environment` key is `services` key which describes each services to be managed by `docker-compose`. `services` key itself contains `app` key and `db` key which are the names of each service. Let's inspect `app` first. The first key of `app` is `build`. ```yaml build: context: .. dockerfile: .devcontainer/Dockerfile ``` As its name implies, `build` key defines how to build the service container. Here we can see that the build context is `..` (parent directory), and the service container should be built by using `.devcontainer/Dockerfile`. Also, note that the path for `dockerfile` is relative to `context` and not the current directory. Next key in `app` is `environment`, and it defines environment variables for the service container. ```yaml environment: <<: *default-environment PORT: 3000 ``` Do you remember that we defined `default-environment` anchor in the beginning? `<<: *default-environment` means "use all values in `default-environment` as values for `environment`". Also, we can see we have another environment variable `PORT` defined after alias. `PORT` variable will be used by our express server to find out which port to listen to. ```yaml ports: - 3000:3000 ``` Because of the environment variable `PORT`, our express server will listen to the port 3000. We need to forward the host's port to container's port in order to access the express server. `ports` key here defines this port-forwarding behavior. Here's the config that makes development exciting! ```yaml volumes: - ..:/workspace ``` `volumes` key can mount a directory of the host machine to a directory inside the service container. In this case, we are mounting VSCode's workspace to `/workspace` inside `app` service container. This will allow us to connect to `app` service container during development, and edit file inside the container. The changes made inside the container will be synced to the host machine! This is what makes this tutorial's setup so amazing. Service containers are purely defined by `Dockerfile`, and we can develop inside that perfect world. Whatever works inside our container will work in production, and we don't need to worry about installing correct version of Python or MySQL or anything on our host machine! Okay, let's check out the last two configs of `app`. ```yaml user: vscode command: sleep infinity ``` `user` key defines the default user of the service container, and `command` defines what to execute after the service container is started. By default, service containers stops after the command exits, so we use `sleep infinity` to keep our service container alive. Now that's the end of `app` service configuration. Name of the second service is `db`. Unlike `app` service, we will not use `Dockerfile` to build this service container. Instead, we will use one of docker images available from **Docker Hub**. ```yaml image: "postgres:12" ``` When we define an `image` key, `docker-compose` will get the specified docker image from **Docker Hub** and build the service container from that image. In this case, we are using `postgres:12` image. ```yaml restart: unless-stopped ``` `restart` key defines the restart policy of the service container. Value of `unless-stopped` always restarts the container if not explicitly stopped. ```yaml environment: *default-environment ``` Here we can find an yaml alias again. This time, we are taking `default-environment` as-is, without overriding any value. ```yaml ports: - 5432:5432 ``` Default port of PostgreSQL server is 5432. We forward this port to allow access to PostgreSQL. ```yaml volumes: - pgdata:/var/lib/postgresql/data - ../postgresql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d ``` We are mounting two volumes to `db` service container. The first volume `pgdata` is for saving database files. Database, roles, tables and functions will be stored in this volume. Note that `pgdata` is not a relative path. `pgdata` is something called **docker volume**, and it is a special storage space managed by docker that can be mounted to containers. The second volume `../postgresql/docker-entrypoint-initdb.d/` is a directory for initialize scripts. If there is no existing database files, `postgres` container creates a database and a user, then runs all scripts inside `/docker-entrypoint-initdb.d` directory. We need a `todo` table for our app, so our initialize script is a SQL script with a `CREATE TABLE` statement. ```yaml volumes: pgdata: ``` Top level `volumes` key is for defining reusable volumes. The empty key `pgdata` here creates a docker volume with default driver. --- ## devcontainer.json [reference](https://code.visualstudio.com/docs/remote/containers#_devcontainerjson-reference) ```json { "name": "Node.js", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", "settings": { "terminal.integrated.shell.linux": "/bin/bash" }, "extensions": [] } ``` `devcontainer.json` file configures "Remote - Containers" extension. - `dockerComposeFile` : path of docker compose files relative to `devcontainer.json` file. - `service` : The name of the service VS Code should connect to once running. - `workspaceFolder` : Sets the default path that VS Code should open when connecting to the container. - `settings` : Adds default `settings.json` values into a container. - `extensions` : An array of extension IDs that specify the extensions that should be installed inside the container when it is created. --- ## Open Workspace in Dev Container We have finished writing all three configuration files. Let's try opening our workspace in a dev container. First, start VS Code if you haven't already. Then, select **"File > Open Folder..."** to open project root. ![First Local](screenshots/first_local.PNG) On the bottom left side of the window, you can see the **"Open a remote window"** button (the blue rectangular button on the screenshot above). Click on it. ![Remote Menu](screenshots/remote_menu.PNG) VS Code will open a menu like the screenshot above. Select **"Remote-Containers: Reopen in Container"**. ![Loading](screenshots/loading.PNG) It will take some time until docker images are built & containers are started. After a few minutes (or few seconds, depends on your machine) VS Code will open your workspace in the dev container. --- ## Conclusion **Congratulations!** You have successfully opened your VS Code workspace inside a dev container alongside with a postgres service. This repository provides a super simple TODO API server in `app.js` file. Start the server and try it yourself! ```bash npm ci node app.js ``` #### REST API | Path | Method | Parameters | Description | |----------------|--------|--------------|--------------------| | / | GET | | Hello, World! | | /todo | POST | task | Create TODO | | /todo | GET | | List TODO | | /todo/finished | POST | id, finished | Update TODO Status | | /todo | DELETE | id | Delete TODO |