Docker + Golang + Gin + Air で開発環境作る

Air とは

cosmtrek/air - github

ホットリロードと呼ばれる機能で俗に言う watch みたいなもの。
今まで realize を使用してきたけど、開発が止まっていて、さらに最新の go にインストールできなかったので、
Air をインストールすることにした。

構成

go ファイルは動くことを確認するだけなので適当に作る。
フレームワークは gin を使用する。

.gitignore
app
  ├ .air.toml
  ├ go.mod
  ├ go.sum
  ├ main.go
  └ src
      └ Sample.go
docker-compose.yaml
go
  └ Dockerfile
nginx
  └ default.conf

サンプルコード

.gitignore

.DS_Store

air.log
main

!.gitkeep

app/.air.toml

本家のサンプル を少し書き換え。

# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
# cmd = "go build -o ./tmp/main ."
cmd = "go build -o ./main ."
# Binary file yields from `cmd`.
# bin = "tmp/main"
bin = "main"
# Customize binary, can setup environment variables when run your app.
# full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
full_bin = "APP_ENV=dev APP_USER=air ./main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Watch these files.
include_file = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test\\.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 0 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms
# Rerun binary or not
rerun = false
# Delay after each executions
rerun_delay = 500
# Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'.
# args_bin = ["hello", "world"]
args_bin = []

[log]
# Show log time
time = false
# Only show main log (silences watcher, build, runner)
main_only = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true

app/go.mod

後で確認する際に、コマンドで生成する。

module github.com/docker-sample

go 1.19

require github.com/gin-gonic/gin v1.8.2

require (
	github.com/gin-contrib/sse v0.1.0 // indirect
	github.com/go-playground/locales v0.14.0 // indirect
	github.com/go-playground/universal-translator v0.18.0 // indirect
	github.com/go-playground/validator/v10 v10.11.1 // indirect
	github.com/goccy/go-json v0.9.11 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/leodido/go-urn v1.2.1 // indirect
	github.com/mattn/go-isatty v0.0.16 // indirect
	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
	github.com/ugorji/go/codec v1.2.7 // indirect
	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
	golang.org/x/net v0.4.0 // indirect
	golang.org/x/sys v0.3.0 // indirect
	golang.org/x/text v0.5.0 // indirect
	google.golang.org/protobuf v1.28.1 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
)

go.sum

割愛

app/main.go


package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/docker-sample/src"
)

func main() {
	// Sample.go を呼び出せるか確認しているだけ
	s := new(src.Sample)
	fmt.Print(s.Test())

	// Getting started にあるサンプルをコピペして少し書き換え
	r := gin.Default()

	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "hello world",
		})
	})

	// r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
	r.Run(":8080")
}

app/src/Sample.go

main.go で呼び出せるか確認するだけのファイル。


package src

type Sample struct {}

func (s *Sample) Test() string {
	return "\n===============\ntest in Sample.go\n===============\n\n"
}

docker-compose.yaml


version: "3"

services:
    app:
        depends_on:
            - go
        image: nginx:1
        volumes:
            - ./app:/var/www/html
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
        ports:
            - 8080:80
        tty: true
        working_dir: /var/www/html
    go:
        build:
            context: .
            dockerfile: ./go/Dockerfile
        volumes:
            - ./app:/var/www/html
        working_dir: /var/www/html

go/Dockerfile

FROM golang:1.19

RUN go install github.com/cosmtrek/air@latest

CMD ["air", "-c", ".air.toml"]

nginx/default.conf

upstream nginx_sample {
   server go:8080;
}

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    root /var/www/html;

    proxy_redirect                          off;
    proxy_set_header Host                   $host;
    proxy_set_header X-Real-IP              $remote_addr;
    proxy_set_header X-Forwarded-Host       $host;
    proxy_set_header X-Forwarded-Server     $host;
    proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://nginx_sample/;
    }

    index index.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
    	root   /usr/share/nginx/html;
    }


    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\.ht {
    	deny  all;
    }
}

確認

# go.mod ファイルを作る
$ cd path/to/your/project/app
$ go mod init github.com/docker-sample

# ビルドする
$ cd path/to/your/project
$ docker-compose up --build

# gin のインストールが失敗する場合は go のイメージを元にした方のコンテナにログインする
# コンテナ確認
$ docker ps
# ログイン
$ docker exec -it your-go-app1 /bin/bash
# gin をインストールする
$ go mod tidy
# コンテナから出る
$ exit

# 一度コンテナを停止する
ctl + c

# コンテナ起動する。 --build はつけない
$ docker-compose up

# http://localhost:8080/ にアクセスして
# main.go などを更新してホットリロードが動いているか確認する。