DockerでGoの開発環境を構築する方法

Goを書く環境を、Dockerのコンテナで作りました。

Macに直接Goを入れて、環境を汚したくなかったためです。

この記事は、DockerComposeでコンテナを立ち上げ、VSCodeからコンテナに入ってHelloWorldを動かすまでの記録です。

途中で出たエラーと、VSCodeのGo拡張を入れたときにつまずく点も書いています。

忙しい人向け

  • golang:1.22.2-alpineのイメージでコンテナを作り、VSCodeのDevContainers拡張で実行中のコンテナにアタッチします。

  • repository name ... must be lowercaseのエラーは、services:の下のサービス名に大文字があると出ます。

    DockerComposeはイメージ名を「プロジェクト名-サービス名」で付けますが、イメージ名には小文字しか使えないためです。

  • Go1.22.2のままだと、Go拡張が入れるgopls(Goの補完や定義ジャンプを担うツール)の最新版は入りません。

    goplsは直近2つのGoの版しかサポートしないためです。

    Go1.22.2では、goplsのv0.16.2なら入りました。

前提条件

  • Mac用のDockerDesktopが入っている。
  • VSCodeにDevContainers拡張が入っている。
  • Goのソースは、Macのフォルダをコンテナにマウントして置く。

発生環境

最初にこの記事を書いたときは、次の環境でした。

  • OS: macOS(Sonoma)
  • エディタ: VSCode
  • Go: 1.22.2

記事を書き直すときに、同じ構成を手元のMacで試し直しました。

この記事のコマンドの結果は、そのときのものです。

  • Mac: AppleSilicon、macOS 27.2
  • DockerEngine: 29.1.3
  • DockerCompose: v5.0.0
  • イメージ: golang:1.22.2-alpine(Alpine 3.19.1、go versionの結果はgo1.22.2 linux/arm64)

プロジェクトの構成

プロジェクト名は、AWSのSSM(パラメータストア)で試したいことがあったので、それに合わせて付けました。

AWS-SDK-GO-V2-SSM/
├── ssmTest/
│   ├── docker/
│   │   └── dockerfile
│   └── src/            # コンテナの /ssmTest にマウントする
└── docker-compose.yml

VS Code のエクスプローラーに表示したプロジェクト構成(ssmTest/docker/dockerfile、src、docker-compose.yml)

ファイル一式はGitHubのリポジトリに置いています。

手順

dockerfileを書く

公式のgolangイメージのうち、軽いAlpine版を使いました。

FROM  golang:1.22.2-alpine

WORKDIR /src

RUN apk update && \
    apk upgrade

docker-compose.ymlを書く

version: "3.8"
services:
  aws-sdk-go-v2-ssm:
    container_name: AWS-SDK-Go-v2-ssm
    build:
      context: .
      dockerfile: ./ssmTest/docker/dockerfile
    tty: true
    volumes:
      - ./ssmTest/src:/ssmTest
    ports:
      - 8080:8080

tty: trueは、コンテナを起動したままにしておくための指定です。

このイメージは起動すると/bin/shを実行するだけなので、これが無いとすぐ終了してしまいます。

tty: trueを付けずに起動して確かめると、2秒後にはExited (0)になっていました。

止まったコンテナには、VSCodeからアタッチできません。

volumesで、MacのssmTest/srcをコンテナの/ssmTestにマウントしています。

コンテナの中で書いたソースは、Macのフォルダにそのまま残ります。

先頭のversion: "3.8"は、今のDockerComposeでは使われていません。

起動すると次の警告が出ますが、動作には影響しません(DockerComposeの公式ドキュメント)。

the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion

コンテナを起動する

docker-compose.ymlがあるディレクトリで、次のコマンドを実行します。

docker compose up -d --build

--buildでイメージを作り、-dでコンテナを裏で動かしたままにします。

大文字のサービス名で出るエラー

services:の下のサービス名をAWS-SDK-Go-v2-ssmのように大文字で書くと、起動のときに次のエラーになります。

unable to get image 'aws-sdk-go-v2-ssm-AWS-SDK-Go-v2-ssm': Error response from daemon: invalid reference format: repository name (library/aws-sdk-go-v2-ssm-AWS-SDK-Go-v2-ssm) must be lowercase

原因は、DockerComposeが付けるイメージ名です。

image:を書かずにbuild:だけを書くと、イメージ名は「プロジェクト名-サービス名」になります。

小文字のサービス名で起動したときのログでも、イメージ名は次のようになっていました。

naming to docker.io/library/aws-sdk-go-v2-ssm-aws-sdk-go-v2-ssm:latest done

プロジェクト名は、docker-compose.ymlがあるディレクトリの名前から付きます。

ディレクトリ名はAWS-SDK-GO-V2-SSMと大文字ですが、プロジェクト名は小文字のaws-sdk-go-v2-ssmになっていました(プロジェクト名の規則)。

一方、サービス名はそのままイメージ名に入ります。

Dockerのイメージ名に使えるのは小文字・数字・区切り記号だけなので(イメージ名の文法)、大文字が混ざると上のエラーになります。

サービス名を小文字にすれば直ります。

container_nameは大文字のままでも、エラーにはなりませんでした。

コンテナ名はイメージ名とは別の扱いだからです。

VSCodeからコンテナにアタッチする

VSCodeの左下にある、大なり小なりが重なったようなボタンを押します。

ボタンが無いときは、DevContainers拡張が入っていません。

VS Code で docker-compose.yml を開いた画面(左下にリモートウィンドウを開くボタン)

出てきたメニューから「実行中のコンテナーにアタッチ…」を選びます。

コマンドパレットからDev Containers: Attach to Running Container...を実行しても同じです(VSCodeの公式ドキュメント)。

VS Code のリモートウィンドウのメニュー(「実行中のコンテナーにアタッチ…」を含む)

起動したコンテナを選ぶと、コンテナの中を開いた新しいウィンドウが立ち上がります。

「フォルダーを開く」で、volumesで指定した/ssmTestを開きます。

dockerfileのWORKDIRは/srcですが、ソースを置くのはマウントした/ssmTestのほうです。

HelloWorldを動かす

VSCodeのターミナル(コンテナの中)で、モジュールを作ります。

go mod init main
go: creating new go.mod: module main

main.goを作り、HelloWorldを表示するだけのソースを書きます。

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

実行します。

go run main.go
Hello, World!

これで、Dockerのコンテナの中でGoを動かせる状態になりました。

Go拡張とツールを入れる

コンテナの中で開発を続けるなら、VSCodeのGo拡張も入れておきます。

コンテナにアタッチしたウィンドウの拡張機能の画面で、一番上に出る「Go」です。

コンテナにアタッチした VS Code の拡張機能画面で、Go 拡張機能(Go Team at Google)を表示したところ

入れたら、command+shift+pでGo: Install/Update Toolsを選び、すべてにチェックを入れて「OK」を押します。

補完や定義ジャンプを担うgoplsなどのツールが、コンテナの中に入ります。

最初にこの記事を書いたときは、Go1.22.2でもこれで入りました。

ところが、書き直すときに同じイメージでgoplsの最新版を入れようとすると、次のエラーになりました。

go install golang.org/x/tools/gopls@latest
go: golang.org/x/tools/gopls@latest: golang.org/x/tools/gopls@v0.24.0 requires go >= 1.27.0 (running go 1.22.2; GOTOOLCHAIN=local)

goplsの最新版が、Go1.27以上を求めているためです。

goplsの公式ドキュメントには、サポートするのは直近2つのGoの版だけだと書かれています。

時間がたつと、同じGoの版のままでも最新のgoplsは入らなくなります。

Goには、必要な版のGoを自動で取ってくる仕組み(GOTOOLCHAIN)がありますが、公式のイメージではGOTOOLCHAIN=localになっていて、入っているGoしか使いません(Goのツールチェーンの説明)。

Goの版を上げられないときは、古い版のgoplsを指定して入れます。

Go1.22.2では、v0.17.1はrequires go >= 1.23.1で入らず、v0.16.2は入りました。

go install golang.org/x/tools/gopls@v0.16.2
golang.org/x/tools/gopls v0.16.2

新しく環境を作るなら、dockerfileのFROMを新しいGoのイメージにするほうが手間はかかりません。

参考リンク