Docker上にGo開発環境を構築し、簡単なゲーム(CLIまたはGUI)を作成しながらGo言語を学びます。
動作環境は、以下の通りです。
- Windows 11
- WSL2 + Ubuntu
- Docker Desktop(WSL2バックエンド)
- Visual Studio Code + Remote – Containers 拡張
環境の詳細は、こちらの記事を参照してください。
DockerでGo学習環境を構築
プロジェクトディレクトリ作成
mkdir go-game-docker
cd go-game-docker
mkdir game
【ディレクトリ構成】
go-game-docker # Dockerfileとdocker-compose.ymlを配置
└── game # ゲームのソースファイルを配置
コンテナを起動し go.mod を初期化する
go mod を初期化して、モジュールモードでのビルドや依存解決を出来るようにします。go.mod を初期化することで、Go のモジュール機能をフル活用し、依存管理・ビルドの再現性・開発自由度・バージョニング運用を安全かつ効率的に行えるようになります。
#go.mod の初期化をコンテナを起動して行う
docker run --rm -v "$(pwd)":/app -w /app golang:1.22 bash -c "go mod init go-game-docker && go mod tidy"
【参考】
go mod init
go mod init go-game-docker は、Go のモジュール(依存管理)機能を使うために必要なコマンドです。
このコマンドによりgo.modファイルが作成され、使用しているライブラリ(依存パッケージ)を宣言できます。
書式:go mod init <module-path>
go mod tidy
Go モジュールの依存関係を整理・最適化する。
go.mod に列挙されているうち、コード内で参照されていないモジュールを削除します。
永続化指定
Docker ボリューム-v "$(pwd)":/appを指定しておくことで、コンテナ内で生成したgo.modをホストと同期して永続化しています。
ビルドキャッシュのデフォルトパスを変更
非 root ユーザーでは、Goのビルドキャッシュのデフォルトパスに書き込めないため、-e HOME=/app を行いHOMEをマウント先に設定しておく。
#ターミナルでの操作
$ docker run --rm -u 1000:1000 -e HOME=/app -v "$(pwd)":/app -w /app golang:1.22 bash -c "go mod init go-game-docker && go mod tidy"
go: creating new go.mod: module go-game-docker
go: to add module requirements and sums:
go mod tidy
go: warning: "all" matched no packages
~/go-game-docker$ ls -la
total 16
drwxr-xr-x 3 4096 Jul 6 17:18 .
drwxr-x--- 23 4096 Jul 6 17:10 ..
drwxr-xr-x 2 4096 Jul 6 17:10 game
-rw-r--r-- 1 34 Jul 6 17:18 go.mod # ホストに永続化されている
Dockerfileの作成
非root権限でGo 言語のコンパイラを実行するDockerfileを作成する。
# Dockerfile
FROM golang:1.22
# 1) アプリ用ユーザー作成
RUN useradd -m appuser
WORKDIR /app
# 2) ソース一式をコピー&所有権変更
COPY --chown=appuser:appuser . .
# 3) モジュール依存をキャッシュ
USER appuser
RUN go mod download
# 4) 実行
CMD ["go", "run", "game/main.go"]
【参考】useraddコマンドで/home/appuserユーザーを作成する。--chown でファイル所有を appuser にする。USER appuserコマンドで、非 rootユーザーのappuserへ切り替える。
docker-compose.ymlファイルの作成
UID:GIDを指定して、非root権限で起動するdocker-compose.ymlを作成する。
services:
app:
build: .
user: "1000:1000" # WSL2 Ubuntu のデフォルトUID:GIDに合わせる
volumes:
- .:/app
working_dir: /app
command: go run game/main.go
【参考】
user: "1000:1000"
コンテナ内で実行されるプロセスのユーザーID(UID)とグループID(GID)を一般ユーザーに指定する。
volumes: - .:/app:
ホスト(.)とコンテナ(/app)ディレクトリを同期(マウント)して、データーを永続化する。
working_dir: /app
コンテナ内の作業パスを指定する。
数当てゲームのコード作成
gameディレクトリ内に、数当てゲーム(main.go)を作成する。
ゲーム内容
1から100までのランダムな数字をコンピュータが選び、ユーザーがそれを当てるまでヒントを出し続ける。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
target := rand.Intn(100) + 1
var guess int
fmt.Println("【数当てゲーム】1~100を当ててみよう!")
for {
fmt.Print("入力> ")
fmt.Scan(&guess)
switch {
case guess < target:
fmt.Println("もっと大きい!")
case guess > target:
fmt.Println("もっと小さい!")
default:
fmt.Println("正解🎉")
return
}
}
}
【参考】
インポートする外部パッケージ
"fmt": フォーマットされたI/O(入力と出力)を扱うためのパッケージ。
"math/rand": 乱数生成のためのパッケージ。
"time": 時間関連の機能(現在時刻の取得など)を提供します。rand.Seedで乱数のシードを設定するために使われます。
キー入力
fmt.Scan(&guess):入力した整数を読み込み、guess変数に格納する。
fmt.Scan 関数
書式:fmt.Scan(&変数名)
Go言語の標準ライブラリ fmt パッケージの一部で、標準入力(通常はキーボード)からスペース区切りの値を読み込むために使われます。
【ファイル ディレクトリ構成】
~/go-game-docker$ tree
.
├── Dockerfile
├── docker-compose.yml
├── game
│ └── main.go
└── go.mod
イメージのビルドとコンテナの実行
docker compose run --rm --build app
【参考】
docker compose up でコンテナを起動した場合、キー入力を受け付けなかったため、この手順ではdocker compose runで起動している。
docker compose up をオプションなしで実行すると、コンテナはフォアグラウンドで起動して、対話的な操作が可能になるはず。
しかし今回は、キー入力を受け付けなかったためバックグラウンドで起動してしまっている可能性があった。
そのため、
単発で対話的に実行する docker compose runコマンドでフォアグラウンドで実行するようにしている。
#ターミナルでの操作
$ docker compose run --rm --build app
[+] Building 2.1s (13/13) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 353B 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 341B 0.0s
=> [internal] load metadata for docker.io/library/golang:1.22 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM docker.io/library/golang:1.22@sha256:1cf6c45ba39db9fd6db16922041d074a63c935556a05c5ccb62d181034df7f02 1.6s
=> => resolve docker.io/library/golang:1.22@sha256:1cf6c45ba39db9fd6db16922041d074a63c935556a05c5ccb62d181034df7f02 1.6s
=> [internal] load build context 0.1s
=> => transferring context: 10.19kB 0.1s
=> [auth] library/golang:pull token for registry-1.docker.io 0.0s
=> CACHED [2/5] RUN useradd -m appuser 0.0s
=> CACHED [3/5] WORKDIR /app 0.0s
=> CACHED [4/5] COPY --chown=appuser:appuser . . 0.0s
=> CACHED [5/5] RUN go mod download 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.0s
=> => exporting manifest sha256:cac3280c88171d36e1bb86ca029e7f2f418c20ca073b693922ea9681f31d99e5 0.0s
=> => exporting config sha256:b9a254899eacf332073335dffd85189b2377d4c5d991dfb97a7f54861d81009b 0.0s
=> => exporting attestation manifest sha256:d6b5efd34de5b0b7ccf6cfff7ddbde24fab0b028add8887b88d901a6131dc9fd 0.0s
=> => exporting manifest list sha256:a52e17bc33b4c638171b4299082e17f0287f0bda520c3a7967fc05d5b9e13cfc 0.0s
=> => naming to docker.io/library/go-game-docker-app:latest 0.0s
=> => unpacking to docker.io/library/go-game-docker-app:latest 0.1s
=> resolving provenance for metadata file 0.0s
【数当てゲーム】1~100を当ててみよう!
入力> 50
もっと大きい!
入力> 70
もっと小さい!
入力> 60
もっと小さい!
入力> 55
正解🎉
$

