oiio
oiio

Reputation: 45

How to deploy a web app with static files in docker?

I built a web app with echo. Some source in server.go is

package main

import ...

type TemplateRenderer struct {
    templates *template.Template
}

func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    if viewContext, isMap := data.(map[string]interface{}); isMap {
        viewContext["reverse"] = c.Echo().Reverse
    }

    return t.templates.ExecuteTemplate(w, name, data)
}

func main() {
    e := echo.New()
    e.Static("/static", "static")

    renderer := &TemplateRenderer{
        templates: template.Must(template.ParseGlob("public/views/*.html")),
    }
    e.Renderer = renderer

    e.GET("/", func(c echo.Context) error {
        return c.Render(http.StatusOK, "index.html", map[string]interface{}{})
    })

    e.Logger.Fatal(e.Start(":8080"))
}

Project tree

├── helper
│   └── string.go
└─── site
    ├── Dockerfile
    ├── go.mod
    ├── go.sum
    ├── server.go
    ├── public
    │   └── views
    │       └── index.html
    └── static

I can run go run server.go to start server, everything works well. But got error if run it with docker.

Dockerfile

FROM golang:1.15.2-alpine3.12 AS builder
WORKDIR /app
COPY . .
WORKDIR /app/site
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.12
COPY --from=builder /app/site /bin/.
ENTRYPOINT [ "server" ]

Built a docker image by

docker build -t gcr.io/${PROJECT_ID}/myapp -f site/Dockerfile .

Run app in docker

docker run --rm -p 8080:8080 gcr.io/${PROJECT_ID}/myapp
panic: html/template: pattern matches no files: `public/views/*.html`

goroutine 1 [running]:
html/template.Must(...)
    /usr/local/go/src/html/template/template.go:372
main.main()
    /app/site/server.go:76 +0x2af

It seems the public folder didn't been copied into image. But there wasn't any error when built the image. What's wrong?

Upvotes: 1

Views: 2501

Answers (2)

colm.anseo
colm.anseo

Reputation: 22097

Firstly, you are using a multi-stage Docker build which is perfect for only copying the compiled binary to the final image. In your Dockerfile, however, you are copying the entire build directory - the binary server as well as all the source.

Secondly, your main problem is when the image is run as a container, the default work directory is / - and thus any paths within your server are not find the html files in /bin/public.

If you ever need to debug a docker image - especially if it is an image based on a linux distro like alpine- simply:

docker run -it myimage /bin/sh

Anyway the 2 simple fixes to your docker:

FROM golang:1.15.2-alpine3.12 AS builder
WORKDIR /app
COPY . .
WORKDIR /app/site
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.12
COPY --from=builder /app/site/server /bin
COPY --from=builder /app/site/public /public
ENTRYPOINT [ "/bin/server" ]

Upvotes: 4

David Maze
David Maze

Reputation: 159403

In Go 1.16 you can compile these files into the binary itself. So you'll need to upgrade the Go toolchain on your host system, and also the FROM line in your build stage in the Dockerfile. Go 1.16 adds the embed package and a new //go:embed directive to support this.

First, you need to tell the compiler to embed the template files, building a filesystem object:

import "embed"

// templateFiles contains the raw text of the template files.
//go:embed public/views/*.html
var templateFiles embed.FS

Then, when you go to use it, Go 1.16 also adds a corresponding ("html/template").ParseFS function:

renderer := &TemplateRenderer{
    templates: template.Must(template.ParseFS(templateFiles)),
}

Now all of the files are embedded in the binary itself, and you shouldn't get "file not found" type errors. You might consider copying only the compiled binary and not anything else into the final image.

# Upgrade to Go 1.16
FROM golang:1.16-alpine3.12 AS builder
# Unchanged from original
WORKDIR /app
COPY . .
WORKDIR /app/site
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.12
# Only copy the compiled binary and not the source tree
COPY --from=builder /app/site/server /bin
# Generally prefer CMD to ENTRYPOINT
CMD [ "server" ]

Upvotes: 3

Related Questions