Sam Wood
Sam Wood

Reputation: 418

Why does my Go web server return "404 page not found" when run inside a Docker container?

I'm building a small, basic web server in Go. If I compile locally and run it, it works great - no issues. The pages show, it's accessible from the localhost, styling intact - all good.

If I then do it inside a Docker container, it doesn't work. It returns "404 page not found". It's like it doesn't have any of the static assets... but this surely can't be - the static assets are intentionally embedded in to the binary using "//go:embed"... and as I said, if you build and run locally it works fine.

I've tried everything I can think of... some steps listed below:

  1. Various different docker images (alpine, ubuntu, golang, golang alpine etc)
  2. Using go:embed in different ways i.e. different patterns
  3. I put some basic logging in to the server code... but it doesn't seem to return any errors so hasn't helped
  4. Changed ports/address

This is just some of the stuff I've tried, with no luck.

I've excluded the css below, it's not really relevant as the index page doesn't even show never mind any styling.

CODE: server code

DOCKERFILE:

FROM golang:1.16.0-alpine3.13 AS build

WORKDIR /app

COPY . .

RUN go build -o server .

FROM golang:1.16.0-alpine3.13

WORKDIR /app

COPY --from=build /app/server .

EXPOSE 8080

CMD ["./server"]

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GOOO</title>
    <link rel="stylesheet" href="/assets/static/css/style.css">
</head>
<body>
    <h1>GO!!!</h1>
</body>
</html>

DIRECTORY STRUCTURE:

├── Dockerfile
├── go.mod
├── go.sum
├── server.go
├── server_test.go
├── static
│   ├── css
│   │   └── style.css
│   └── index.html

Upvotes: 0

Views: 2334

Answers (1)

Brits
Brits

Reputation: 18370

As per the comments the main issue you are having is that you are serving index.html from the file system (not the embedded filesystem) and the file does not exist there.

A second issue is that the embedded filesystem will contain a single directory static so you need to use something like s, err := fs.Sub(static, "static") so that s.Open("index.html") will work (otherwise you would need static.Open("static/index.html") - this applies to your http.FileServer as well as when serving index.html).

Note: You may not need the below because you could just run http.FileServer for the / path (so it serves index.html and the files in the subdirectories). http.FileServer will automatically serve index.html if no filename is provided in the url.

To serve index.html from the embedded filesystem you could rewrite your function as (untested!):

// default/root handler which serves the index page and associated styling
func indexHandler(w http.ResponseWriter, r *http.Request) {
    f, err := s.Open("index.html") // Using `s` from above and assumes its global; better to use handlerfunc and pass filesystem in

    if err != nil {
        // Send whatever error you want (as file is embedded open should never fail)
        return
    }
    defer f.Close()

    w.Header().Set("Content-Type", "text/html")
    if _, err := io.Copy(w, f); err != nil { // Write out the file
        // Handle error
    }
}

The above relies upon a global variable (which I'm not keen on) so I'd transform this into something like:

func IndexHandlerFunc(fs fs.FS, h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        f, err := fs.Open("index.html")

        if err != nil {
            // Send whatever error you want (as file is embedded open should never fail)
            return
        }
        defer f.Close()

        w.Header().Set("Content-Type", "text/html")
        if _, err := io.Copy(w, f); err != nil { // Write out the file
            // Handle error
        }
    })
}

Upvotes: 1

Related Questions