Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebuilding golang app in docker upon file change

Tags:

docker

go

I am building a golang backend with gin and I want to set it up as a service in docker. I want that when I make changes to the code the app should rebuild with the changes. I have tried to use 2 golang packages github.com/cespare/reflex and github.com/cortesi/modd.

For the cespare/reflex package I found an unsolved github issue where if you use a graceful shutdown for gin which I use the app doesn't get the SIGTERM/SIGINT from reflex. So I moved on to the second package. See below:

My Dockerfile for the golang backend is:

FROM golang:1.22.5-alpine3.20 AS builder

WORKDIR /go/src/app

ENV GO111MODULE=on

RUN go install github.com/cortesi/modd/cmd/modd@latest

COPY go.mod .
COPY go.sum .

RUN go mod download

COPY . .

RUN go build -o ./run .

FROM alpine:3.20
RUN apk --no-cache add ca-certificates
WORKDIR /root/

COPY --from=builder /go/src/app/run .

EXPOSE 8080
CMD ["./run"]

My docker-compose.yaml file is partially:

services:
  goapp:
    build:
      context: ./src
      target: builder
    image: goapp
    [...]
    volumes:
      - ./src:/go/src/app
    command: modd -f .docker/modd.conf

My modd.conf file:

**/*.go {
    prep: echo "file changed"
    prep: go build -o ./run .
    daemon +sigterm: ./run
}

I also tried to add prep: pkill -f run || true at the beginning of the modd.conf but it just kills the container.

With the configuration above modd doesn't detect any changes made to the code.

If you have any solution to what I try to acomplish please let me know. I am also ok with solutions that use other packages then the 2 mentioned.

like image 466
iLaurian Avatar asked Oct 28 '25 02:10

iLaurian


1 Answers

There is no need for third party apps any more. Since docker compose version 2.22.0, you can simply use docker compose up --watch.

Basically, you instruct docker to watch over various files and folders and instruct it regarding the action to take if one of those files or folders change.

The project

Imagine a simple application setup like this:

.
├── Dockerfile
├── app
│   ├── go.mod
│   └── main.go
├── docker-compose.yaml
└── resources
    └── sample.html

main.go

We are writing a very simplistic web server:

package main

import (
    "flag"
    "log/slog"
    "net/http"
)

var bindAddress = flag.String("bind", ":8080", "HTTP server bind address")
var dir = flag.String("dir", "./", "Directory to serve")


// Do not use this code in production. There are security implications which are not mitigated for the sake of brevity. You have been warned!
func main() {
    flag.Parse()
    slog.Info("Starting server", "addr", *bindAddress, "dir", *dir)

    http.Handle("GET /files/", http.StripPrefix("/files/", http.FileServer(http.Dir(*dir))))
    err := http.ListenAndServe(*bindAddress, nil)
    if err != nil && err != http.ErrServerClosed {
        slog.Error("Failed to start server", "err", err)
    }
}

Note: Do NOT use the code above in production. There are security implications which are not mitigated for the sake of brevity. You have been warned!

The important parts are the docker-compose.yaml and the Dockerfile.

Dockerfile

First, the Dockerfile so we understand what is going to happen:

FROM golang:1.22-alpine3.20 AS builder
WORKDIR /app
ADD app/ .
RUN go build -o mycoolapp .

FROM alpine:3.20
COPY --from=builder /app/mycoolapp /usr/local/bin/mycoolapp
COPY resources/sample.html /usr/local/share/mycoolapp/sample.html
ENTRYPOINT [ "/usr/local/bin/mycoolapp" ]
CMD [ "-dir","/usr/local/share/mycoolapp/" ]

So basically our simple webserver is built and will read the content it serves from /usr/local/share/mycoolapp/ in the service container.

docker-compose.yaml

services:
  my-service:
    image: my-service:latest
    build:
      context: .
      dockerfile: Dockerfile
    develop:
      watch:
        - action: sync
          path: resources/
          target: /usr/local/share/mycoolapp/
        - action: rebuild
          path: app/
    ports:
      - "8080:8080"
    environment:
      - MY_ENV_VAR=foo

So what happens when we "start" this compose file (docker compose up --watch)?

  1. If my-service:latest does not exist on your machine, it is built.
  2. If a file is changed, added or removed in resources, this will be reflected by docker syncing those changes to the directory /usr/local/share/mycoolapp/ in the service container, which is the default directory from which the webserver within our container is configured to serve files. However, the files are not added to the image.
  3. If a file is changed, added or removed in app, a full rebuild is triggered as if docker compose build was called.
like image 128
Markus W Mahlberg Avatar answered Oct 30 '25 22:10

Markus W Mahlberg



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!