I want to create the strongest local development environment using VSCode Remote Containers

Introduction

Introducing your own settings for Remote Containers, a VS Code god extension Official sample is open to the public, but since it is difficult to use as it is, I will introduce the settings edited to make it easy for me to use. In addition, all the configuration files introduced in this article are published in the following repositories (including those in environments not introduced). https://github.com/sabure500/remote-container-sample

Also, I thought it would be good to use Remote Containers, so I'm playing around with the settings to make it easier to use, but since this article "wants to create" the strongest local environment, there is an idea that this is better. I would be grateful if you could tell me

What is VSCode Remote Containers?

It's an extension of VS Code that allows you to open and work in VS Code inside a container. Since you can open VS Code directly in the container and work, you can sandbox the development environment and develop in a place that does not affect the local machine at all. There are other similar extension series, "Remote SSH" and "Remote WSL", which allow you to open VS Code and work in the SSH destination or WSL respectively. Please refer to Official Site for details of each.

Installation

To create a development environment with VSCode Remote Containers, you need to install the following two Conversely, if you have the following two, you can create an environment such as Node, python, Go, Java etc. without putting anything else on the local machine.

Docker Desktop for Windows or Mac Download the installer from the official page below https://www.docker.com/products/docker-desktop

Visual Studio Code

Installation of VS Code itself

Download from the official page below https://code.visualstudio.com/docs

Introduction of Remote Containers

Since Remote Containers is a normal extension, you can install it from there as it will appear in the list by starting it after installing VS Code, selecting the extension from the tab on the left and searching for "Remote Container". Alternatively, you can install it from the marketplace page below. https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers

Launch Remote Containers

After starting the location where the Remote Containers configuration file, which will be introduced later, is located as Workspace, click the green "> <" mark at the bottom left of VS Code and select "Remote-Containers: Reopen in Container".

Introduction of environment construction settings

Environment construction in Remote Containers is done by placing devcontainer.json, a configuration file for Remote Containers, and Dockerfile (or docker-compose.yaml, etc.) in the .devcontainer directory. I will introduce the settings for each environment

GoogleCloudSDK I also use Remote Containers when using Google Cloud SDK commands in my local environment. The configuration file is published below, and basically anyone can use the same Google Cloud SDK environment immediately by using this as it is. Describe the settings for building the environment The whole directory structure is as follows

.
├ .devcontainer
    ├ devcontainer.json
    ├ Dockerfile
    ├ .config/fish/config.fish
    └ .local/share/fish/fish_history

Dockerfile File for creating a container to be used as an actual development environment First give the big picture, then explain each line

Dockerfile


FROM google/cloud-sdk:297.0.1-alpine

# ===== common area =====
RUN apk add --no-cache fish git openssh curl
COPY .config/fish/config.fish /root/.config/fish/config.fish
# =======================

# ===== kubernetes resource install =====
ENV KUBECTL_VERSION 1.18.4
ENV KUSTOMIZE_VERSION 3.1.0
ENV ARGOCD_VERSION 1.5.2
RUN curl -sfL -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl \
    && curl -sfL -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 \
    && curl -sfL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v${ARGOCD_VERSION}/argocd-linux-amd64 \
    && chmod +x /usr/local/bin/kubectl /usr/local/bin/kustomize /usr/local/bin/argocd
# =======================

Base image uses google / cloud-sdk: 297.0.1-alpine

Since the base image is alpine, use apk to install the package you want to use in the development environment Here, fish, git, ssh, curl are included, but if you want to use bash, you can customize it by introducing bash instead of fish.

Use a fish shell as a working shell Since it is difficult to use with the default settings, copy the configuration file to change the prompt such as displaying the Git branch and place it on the container The fish configuration file is based on the following blog post. https://www.martinklepsch.org/posts/git-prompt-for-fish-shell.html

Since I often use GKE exclusively as a GCP resource, I have Kubernetes related resources installed in the container.

devcontainer.json Configuration file when opening a container from VS Code Describe the Dockerfile to be used, the extension function when using VS Code on the container, Volume from the local environment, etc. See the official reference (https://code.visualstudio.com/docs/remote/containers#_devcontainerjson-reference) for more details on what else you can do. First give the big picture, then explain each line

devcontainer.json


{
    "name": "Google Cloud SDK Remote-Container",
    "build" : {
        "dockerfile": "Dockerfile"
    },
    "settings": {
        "terminal.integrated.shell.linux": "/usr/bin/fish",
    },
    "extensions": [
        "alefragnani.bookmarks",
        "mhutchie.git-graph",
        "redhat.vscode-yaml",
        "zainchen.json"
    ],
    "mounts": [
        "source=${localEnv:HOME}/.ssh/,target=/root/.ssh/,type=bind,consistency=cached",
        "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached",
        "source=${localWorkspaceFolder}/.devcontainer/.local/share/fish/fish_history,target=/root/.local/share/fish/fish_history,type=bind,consistency=cached",
        "source=${localEnv:HOME}/.config/gcloud/,target=/root/.config/gcloud/,type=bind,consistency=cached",
        "source=${localEnv:HOME}/.kube/,target=/root/.kube/,type=bind,consistency=cached",
    ],
}

Specify the location of the Dockerfile As shown in the directory structure at the beginning, since it is on the same directory, it is written as "Dockerfile" as it is

Describe the VS Code settings you want to set independently on the container For example, settings for Extension that are not installed locally but are installed on the container, etc. Regarding the description of settings, what is written locally is inherited on the container without writing it again on devcontainer.json Here, on the container, the terminal shell only describes using fish.

Describe the extension you want to use in the container environment Regarding the extension, unlike the settings of settings, even if it is installed locally, those that are not written in devcontainer.json will not be installed on the container, so be careful.

Mount files that you want to use in your local environment or that you don't want to be erased when you restart the container "source =" specifies the local path, "target =" specifies the path on the container You can also use the environment variable "XXXX" on the local environment by writing $ {localEnv: XXXX}. Here we have 5 files and directories mounted

  1. ssh settings Since ssh settings are used from multiple environments, mount and use from the local environment
  2. git settings Since git settings are also used from multiple environments, mount and use from the local environment
  3. Fish operation history This is quite a point, and if you are working on a container, if you stop the container, all the operation history will be deleted. It was inconvenient for me to use the command history of the past history by using the cursor ↑ etc. when working, so it is inconvenient to erase it, so mount it on Workspaces so that it will not disappear
  4. gcp settings Since it is troublesome if GCP login information etc. disappears every time, mount it from local
  5. kubectl settings Similarly, it is troublesome if GKE's cluster registration etc. disappears every time, so mount it locally

Java As an environment for Java, I am using an environment that includes OpenJDK + Wildfly + maven + Gradle. This Java environment will also be introduced focusing on the differences from the Google Cloud SDK environment introduced above. The whole directory structure is as follows

.
├ .devcontainer
    ├ devcontainer.json
    ├ docker-compose.yaml
    ├ Dockerfile
    ├ .m2/
    ├ .gradle/
    ├ .config/fish/config.fish
    └ .local/share/fish/fish_history

docker-compose Java environment uses docker-compose instead of Dockerfile to use Docker's Network to connect to DB environment set up in a separate container First give the big picture, then explain each line

docker-compose.yaml


version: "3"
services:
  jdk-wildfly-maven:
    build: .
    ports:
      - "8080:8080"
      - "9990:9990"
    command: /bin/sh -c "while sleep 1000; do :; done"
    volumes: 
      - $HOME/.ssh:/root/.ssh
      - $HOME/.gitconfig:/root/.gitconfig
      - .local/share/fish/fish_history:/root/.local/share/fish/fish_history
      - ..:/workspace
      - ./jboss_home/configuration/standalone.xml:/opt/wildfly/standalone/configuration/standalone.xml
      - .m2:/root/.m2
      - .gradle:/root/.gradle
    networks:
      - remote-container_common-network
networks: 
  remote-container_common-network:
    external: true

Wildfly's default ports 8080 and 9990 will be accessed on the container when the target port is accessed in the local environment.

Override the default command so that the container does not stop if the default command at container startup fails or terminates The command described here is the default setting of Remote Containers when docker-compose is not used. When using docker-compose, it is necessary to write it explicitly

Mounts from local environment should be written in docker-compose.yaml instead of devcontainer.json The difference from the case of Dockerfile is that the workspace itself is explicitly specified and mounted as written as "..: / workspace". Gradle and Maven settings and repositories are mounted with ".m2: /root/.m2" and ".gradle: /root/.gradle" as settings unique to the java environment. Since this is only used in this environment, it is mounted directly on the WorkSpace instead of the user home ($ HOME). Also, regarding the Wildfly configuration file standalone.xml, the data source settings etc. may always be changed during development, and it is a problem if it is deleted every time the container is restarted. It is mounted in the form of standalone.xml: /opt/wildfly/standalone/configuration/standalone.xml "

Java is used as an application execution environment, so I want to connect to the DB As for DB, it starts as a container in the local environment, so create a Docker Network and use it to connect with other containers. Therefore, in order to use this environment, it is necessary to create a network in advance with the following command.

```bash
docker network create --driver bridge remote-container_common-network
```

Dockerfile Dockerfile to create OpenJDK + Wildfly + maven + Gradle environment specified in docker-compose.yaml As a base image, the JBoss official image "jboss / wildfly" is large and difficult to use, and I wanted to use an alpine-based image, so I created it based on Adoptopenjdk.

Dockerfile


FROM adoptopenjdk/openjdk11:alpine-slim

# ===== common area =====
ENV WORKSPACE_DIR "/workspace"
RUN apk add --no-cache fish git openssh curl wget tar unzip\
    && mkdir -p $WORKSPACE_DIR
COPY .config/fish/config.fish /root/.config/fish/config.fish
# =======================

# ==== wildfly install =====
ENV JBOSS_HOME "/opt/wildfly"
ENV WILDFLY_VERSION "20.0.0.Final"

RUN wget -P /opt http://download.jboss.org/wildfly/${WILDFLY_VERSION}/wildfly-${WILDFLY_VERSION}.tar.gz \
    && tar -zxvf /opt/wildfly-${WILDFLY_VERSION}.tar.gz -C /opt \
    && rm /opt/wildfly-${WILDFLY_VERSION}.tar.gz \
    && mv /opt/wildfly-${WILDFLY_VERSION} ${JBOSS_HOME} \
    && $JBOSS_HOME/bin/add-user.sh admin admin --silent
# =======================

# ==== maven install =====
ENV MAVEN_HOME "/opt/maven"
ENV MAVEN_VERSION 3.6.3
ENV PATH "$PATH:$MAVEN_HOME/bin"
ENV MAVEN_CONFIG "$HOME/.m2"
RUN curl -fsSL -o /opt/apache-maven-${MAVEN_VERSION}-bin.tar.gz http://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
    && tar -zxvf /opt/apache-maven-${MAVEN_VERSION}-bin.tar.gz -C /opt \
    && rm /opt/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
    && mv /opt/apache-maven-${MAVEN_VERSION} /opt/maven
# =======================

# ==== gradle install ====
ENV GRADLE_HOME "/opt/gradle"
ENV GRADLE_VERSION 6.5
ENV PATH "$PATH:$GRADLE_HOME/bin"
RUN curl -fsSL -o /opt/gradle-${GRADLE_VERSION}-bin.zip https://downloads.gradle-dn.com/distributions/gradle-${GRADLE_VERSION}-bin.zip \
    && unzip -d /opt /opt/gradle-${GRADLE_VERSION}-bin.zip \
    && rm /opt/gradle-${GRADLE_VERSION}-bin.zip \
    && mv /opt/gradle-${GRADLE_VERSION} /opt/gradle
# =======================

devcontainer.json The default settings and available settings of devcontainer.json are different depending on whether you use Dockerfile or docker-compose (for example, mount is not effective even if you write it in devcontainer.json, docker-compose Will have to be described in) See Official Reference for details.

devcontainer.json


{
    "name": "JDK&Wildfly&Maven&Gradle Remote-Container",
    "dockerComposeFile": "docker-compose.yaml",
    "service" : "jdk-wildfly-maven",
    "workspaceFolder": "/workspace",
    "settings": {
        "terminal.integrated.shell.linux": "/usr/bin/fish",
        "java.home": "/opt/java/openjdk",
        "maven.executable.preferMavenWrapper": false,
        "maven.executable.path": "/opt/maven/bin",
        "maven.terminal.useJavaHome": true,
        "java.jdt.ls.vmargs": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication -javaagent:\"/root/.vscode/extensions/gabrielbb.vscode-lombok-1.0.1/server/lombok.jar\"",
    },
    "extensions": [
        "alefragnani.bookmarks",
        "mhutchie.git-graph",
        "vscjava.vscode-java-pack",
        "shengchen.vscode-checkstyle",
        "gabrielbb.vscode-lombok",
        "naco-siren.gradle-langua"
    ],
    "shutdownAction": "stopCompose"
}

When using docker-compose, use "dockerComposeFile" and specify the location of docker-compose.yaml Also, in the case of docker-compose, there is a possibility that multiple containers are running, so specify which container to open VS Code with service.

Unlike the case of Dockerfile, in the case of docker-compose, it is necessary to explicitly specify the location of the workspace to be mounted. In this case, you cannot specify a directory that does not exist, so create a directory such as / workspace on the Dockerfile first.

Although it is not included in VS Code of the local environment, the extension you want to use is specified in the Java environment. It also describes the contents of settings that you want to apply only on the container that supports the extension.

MySQL Create a DB to be used from Java created in the previous chapter in a container It doesn't make much sense to create this environment with Remote Containers, but to unify it, I created it with Remote Containers. There is no problem even if you simply start with docker-compose Before creating this environment to connect with other containers (Java environment), create a Docker network with the following command (If you already have it, there is no problem)

docker network create --driver bridge remote-container_common-network

The entire directory structure and configuration file are as follows

.
├ .devcontainer
│  ├ devcontainer.json
│  ├ docker-compose.yaml
│  └ my.cnf
└ db

docker-compose.yaml


version: "3"
services:
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: pass
      TZ: "Asia/Tokyo"
    ports:
      - "3306:3306"
    volumes: 
      - ../db/data:/var/lib/mysql
      - ./my.cnf:/etc/mysql/conf.d/my.cnf
    networks:
      - remote-container_common-network
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      PMA_ARBITRARY: 1
    ports:
      - 3307:80
    depends_on:
      - mysql
    networks:
      - remote-container_common-network
networks: 
  remote-container_common-network:
    external: true

devcontainer.json


{
    "name": "MySQL Remote-Container",
    "dockerComposeFile": "docker-compose.yaml",
    "service" : "mysql",
    "workspaceFolder": "/root",
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash",
    },
    "extensions": [],
    "shutdownAction": "stopCompose"
}

Supplement

This time, I'm building my own local environment, so I'm making Remote Containers settings assuming that multiple applications will be handled in one environment. In other words, the following

.
├ .devcontainer
├ application1
│  ├ .git
│  └ source code
├ application2
│  ├ .git
│  └ source code

It is better to create one Remote Containers setting for one application instead of the above configuration because it has the advantage that the development environment of that application can be managed on git and all developers can use the same environment. I think that the In other words, the following

.
├ application1
│  ├ .devcontainer
│  ├ .git
│  └ source code
├ application2
│  ├ .devcontainer
│  ├ .git
│  └ source code

However, in the case of such a management method, it is better to consult with the team and decide what to do because a war may occur if you insert the original setting of fish as introduced this time.

Advantages and disadvantages of VSCode Remote Containers

End the advantages and disadvantages that you thought you were using at the end

merit

Demerit

reference

Recommended Posts

I want to create the strongest local development environment using VSCode Remote Containers
I want to call the main method using reflection
I tried to build the environment little by little using docker
I tried to create a padrino development environment with Docker
After learning Progate, I tried to make an SNS application using Rails in the local environment
I want to do team development remotely
I tried migrating the portfolio created on Vagrant to the Docker development environment
[Rails] I want to reset everything because the data in the local environment is strange! What to do before that
Rails6.0 ~ How to create an eco-friendly development environment
I want to truncate after the decimal point
I want to get the value in Ruby
[Rough commentary] I want to marry the pluck method
Try to build a Java development environment using Docker
Make the strongest Laravel development environment (Docker) Japan time
I want to return the scroll position of UITableView!
I want to simplify the log output on Android
I want to create a generic annotation for a type
I want to add a delete function to the comment function
Create Chisel development environment with Windows10 + WSL2 + VScode + Docker
I tried to develop the cache function of Application Container Cloud Service in the local environment
I want to recreate the contents of assets from scratch in the environment built with capistrano
I want to put the JDK on my Mac PC
I want to give a class name to the select attribute
I want to create a Parquet file even in Ruby
I tried using Dapr in Java to facilitate microservice development
I want to recursively search the class list under the package
I want to transition to the same screen in the saved state
I want to return multiple return values for the input argument
[Ruby] I want to reverse the order of the hash table
I want to pass the startup command to postgres with docker-compose.
I want to simplify the conditional if-else statement in Java