Docker+Airでホットリロード可能なGo(Echo)の開発環境を構築した話
はじめに
今回は技育キャンプというハッカソン参加を機にGoとDockerによるバックエンドの環境構築に取り組みましたが、この段階で想像以上に時間が掛かってしまったので、共有もかねてこの記事を書くに至りました。
この記事について
AirとDockerでホットリロード可能なGo(Echo)の開発環境の構築に取り組んだ際に出くわしたエラーと解決方法、正しく動く環境を構築するまでの一連の流れを解説します。
必要なもの
Docker for Desktopのインストール
最終的なディレクトリ構成
.
├backend/
├src/
└main.go
├tmp/
└main
└.air.toml
└Dockerfile
└go.mod
└go.sum
└docker-compose.yml
これから手順を解説していきます。
Dockerfile,docker-compose.ymlファイルの作成
まず、以下のようなディレクトリ構成になるようにDockerfileとdocker-compose.ymlファイルを作成していきます。
.
├backend/
└Dockerfile
└docker-compose.yml
FROM golang:1.22-alpine
WORKDIR /app/
# 今はコメントアウトしておく
# COPY backend/go.sum .
# COPY backend/go.mod .
# RUN apk upgrade --update && apk --no-cache add git gcc musl-dev
# RUN go install github.com/cosmtrek/air@latest
# COPY ./backend .
# CMD ["air", "-c", ".air.toml"]
version: "3"
services:
backend:
build:
context: .
dockerfile: ./backend/Dockerfile
ports:
- 8080:8080
volumes:
- ./backend:/app
main.goファイルの作成
次に、以下のようなディレクトリ構成になるようにsrc/main.goファイルを作成していきます。
.
├backend/
├src/
└main.go
└Dockerfile
└docker-compose.yml
そして、main.goには以下のように記述します。
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":8080"))
}
モジュールエラーなどが出ているかもしれませんが、今は無視して次に進んで下さい。
dockerイメージの作成
初めに、Docker for Desktopを起動しておいてください。インストールをしていない場合はインストールを行ってから以下の手順を行ってください。
Docker for Desktopを起動したら、以下のコマンドを実行しイメージを作成します。
docker compose build
ここでエラーが出る場合は、Docker関係のファイルに空白などが含まれていないか確認してみてください。
上記のコマンドを実行できたら、以下のコマンドを実行しコンテナの中に入ります。
docker compose run --rm backend sh
コンテナに入ったら、以下のコマンドを実行します。
backendディレクトリ直下に「go.mod」が作成されます。
go mod init backend
次に、今回はEchoを使用するので以下のコマンドを実行し、Echoモジュールを追加します。そうすることでbackendディレクトリ直下に「go.sum」が作成されます。
go get github.com/labstack/echo/v4
そして、以上の手順を終えたらexitコマンドで一旦コンテナから抜けます。
exit
そうしたら、Dockerfileのコメントアウトを外します。
FROM golang:1.22-alpine
WORKDIR /app/
COPY backend/go.sum .
COPY backend/go.mod .
RUN apk upgrade --update && apk --no-cache add git gcc musl-dev
RUN go install github.com/cosmtrek/air@latest
COPY ./backend .
CMD ["air", "-c", ".air.toml"]
そして、再度以下のコマンドを実行します。
docker compose build
docker compose run --rm backend sh
コンテナに入ったら、以下のコマンドを実行します。そうすると、backend直下に.air.tomlファイルが生成されます。
air init
そうしたら、exitでコンテナから抜けます。
コンテナ起動
ここまでのディレクトリ構成は以下のようになっています。
.
├backend/
├src/
└main.go
└.air.toml
└Dockerfile
└go.mod
└go.sum
└docker-compose.yml
エラーが出るやり方(この部分は行う必要はありません)
以下のコマンドを実行します。
docker compose up
すると、おそらくターミナルに以下のようなコマンドが表示されるはずです。
[+] Building 0.0s (0/0) docker:default
[+] Running 1/1
✔ Container hackathon-backend-1 Recreated 3.2s
Attaching to hackathon-backend-1
hackathon-backend-1 |
hackathon-backend-1 | __ _ ___
hackathon-backend-1 | / /\ | | | |_)
hackathon-backend-1 | /_/--\ |_| |_| \_ v1.51.0, built with Go go1.22.2
hackathon-backend-1 |
hackathon-backend-1 | mkdir /app/tmp
hackathon-backend-1 | watching .
hackathon-backend-1 | watching src
hackathon-backend-1 | !exclude tmp
hackathon-backend-1 | building...
hackathon-backend-1 | no Go files in /app
hackathon-backend-1 | failed to build, error: exit status 1
hackathon-backend-1 | running...
hackathon-backend-1 | /bin/sh: /app/tmp/main: not found
そして、backendディレクトリにtmpディレクトリとエラーファイルが生成されます。
この手順を行った場合はこのtmpディレクトリを削除してから次の手順に進んでください。
Air実装後のエラーの修正
上記のエラーを修正していきます。
.air.tomlファイルを開き、以下の色部分を変更します。
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
- cmd = "go build -o ./tmp/main ."
+ cmd = "go build -o ./tmp/main ./src/main.go"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true
そして、再度
docker compose up
を実行すると、エラーは表示されなくなり、以下がターミナルに表示されます。
[+] Building 0.0s (0/0) docker:default
[+] Running 2/2
✔ Network hackathon_default Created 0.2s
✔ Container hackathon-backend-1 Created 0.5s
Attaching to hackathon-backend-1
hackathon-backend-1 |
hackathon-backend-1 | __ _ ___
hackathon-backend-1 | / /\ | | | |_)
hackathon-backend-1 | /_/--\ |_| |_| \_ v1.51.0, built with Go go1.22.2
hackathon-backend-1 |
hackathon-backend-1 | watching .
hackathon-backend-1 | watching src
hackathon-backend-1 | !exclude tmp
hackathon-backend-1 | building...
hackathon-backend-1 | go: downloading github.com/labstack/echo/v4 v4.12.0
hackathon-backend-1 | go: downloading github.com/labstack/gommon v0.4.2
hackathon-backend-1 | go: downloading golang.org/x/crypto v0.22.0
hackathon-backend-1 | go: downloading golang.org/x/net v0.24.0
hackathon-backend-1 | go: downloading github.com/valyala/fasttemplate v1.2.2
hackathon-backend-1 | go: downloading golang.org/x/sys v0.19.0
hackathon-backend-1 | go: downloading github.com/valyala/bytebufferpool v1.0.0
hackathon-backend-1 | running...
hackathon-backend-1 |
hackathon-backend-1 | ____ __
hackathon-backend-1 | / __/___/ / ___
hackathon-backend-1 | / _// __/ _ \/ _ \
hackathon-backend-1 | /___/\__/_//_/\___/ v4.12.0
hackathon-backend-1 | High performance, minimalist Go web framework
hackathon-backend-1 | https://echo.labstack.com
hackathon-backend-1 | ____________________________________O/_______
hackathon-backend-1 | O\
hackathon-backend-1 | ⇨ http server started on [::]:8080
そして、http://localhost:8080/にアクセスすると、Hello World!と表示されるはずです。
しかし、main.goファイルを変更してもホットリロードが行われません。これを次に修正していきます。
ホットリロードが出来ないエラーの修正
再度.air.tomlファイルを開き、以下の色部分を変更します。
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./src/main.go"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
- poll = false
+ poll = true
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true
そして、再度
docker compose up
を実行し、main.goを変更すると正しくホットリロードが行われるようになります。
ポートのエラーについて
以下のエラーが出た
Port scan timeout reached, no open ports detected. Bind your service to at least one port. If you don't need to receive traffic on any port, create a background worker instead.
==> Docs on specifying a port: https://render.com/docs/web-services#port-binding
これに対し、私はdb.goのNewDBを以下のように修正した。
func NewDB() *gorm.DB {
//変更前
err := godotenv.Load(fmt.Sprintf(".env"))
if err != nil {
log.Print(err)
}
//変更後
if os.Getenv("GO_ENV") == "dev" {
err := godotenv.Load()
if err != nil {
log.Fatalln(err)
}
}
//以下省略
}
同時に、効果があったかは不明だが、mainやプログラムフォルダをbackend/srcからbackend直下に移動し、Dockerfileに以下のコードを追加した。
FROM golang:1.22-alpine
WORKDIR /app/
# ローカルの場合
COPY backend/go.sum .
COPY backend/go.mod .
# RUN apk upgrade --update && apk --no-cache add git gcc musl-dev
COPY ./backend .
/* 追加*/
RUN go build -tags netgo -ldflags '-s -w' -o app
EXPOSE 8080
/*追加ここまで*/
CMD ["go", "run", "main.go"]
参考
Discussion