reactjsnode.jsdockergogo-gin

Server (serving static reactjs files) in Docker container 404 page not found


I am trying to containerize a Go app that serves static files on port 8000. I have looked at other posts on this topic and many seem to say to use router.Run("0.0.0.0:8000") or router.Run(":8000"). I have tried both but still no success. My main.go looks like this:

package main

// required packages
import (

    "fmt"
    "log"
    "os"

    "github.com/gin-gonic/gin"
    "github.com/gin-contrib/cors
    "net/http"
)

func main() {
    // start the server
    serveApplication()

}

func serveApplication() {
    corsConfig := cors.Config {
        AllowOrigins: []string{"*"},
        AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
        AllowCredentials: true,
    }

    router := gin.Default()
    router.Use(cors.New(corsConfig))

    router.StaticFS("/static", http.Dir("./frontend/build/static"))
    router.StaticFile("/", "./frontend/build/index.html")
    router.Run(":8000")
    fmt.Println("Server running on port 8000")
}

And the following Dockerfile:

FROM node:16-alpine3.11 as build-node

WORKDIR /workdir
COPY frontend/ .
RUN yarn install
RUN yarn build

COPY .env /workdir/

FROM golang:1.21-rc-alpine3.18 as build-go
#FROM golang:1.17rc2-alpine3.14 as build-go

ENV GOPATH ""
RUN go env -w GOPROXY=direct
RUN apk add git

ADD go.mod go.sum ./
RUN go mod download
ADD . .
COPY --from=build-node /workdir/build ./frontend/build
RUN go build -o /main

FROM alpine:3.13
COPY --from=build-go /main /main
COPY --from=build-node /workdir/.env .env
EXPOSE 8000
ENTRYPOINT [ "/main" ]

My folder structure looks like this;

portal
- frontend (here the react app is stored)
- backend (all my backend logic)
- Dockerfile
- main.go
- go.mod

When I run it locally with go run main.go the frontend is served correctly on port 8000 and loading http://localhost:8000 works fine. When I build the docker image with docker build -t portal . and then run it with docker run -p 8000:8000 --name portal portal I can see in the terminal that the server starts and says it is running on port 8000, but I always get a 404 page not found error.

I have tried using router.run("0.0.0.0:8000"), router.run("localhost:8000") or docker run --network host --name portal portal.

Is there anything I am missing? Am I copying the frontend build to the wrong place?


Solution

  • The only things in your final image are the things you COPY after the last FROM line; that is, the main binary and the .env file. You're trying to serve files out of ./frontend/... but that's not in the final image. Just moving the relevant COPY line to the final stage should work.

    FROM alpine:3.13
    COPY --from=build-go /main /main
    COPY --from=build-node /workdir/.env .env
    COPY --from=build-node /workdir/build ./frontend/build # <-- move from above
    ...
    

    Conversely, since you're not using something like the embed package to directly embed the built frontend code into the binary, you don't need it during the (Go) build stage.

    It may also work to use embed, and not rearrange your Dockerfile. This would look roughly like

    //go:embed frontend/build/*
    var content embed.FS
    
    router.StaticFS("/static", http.FS(fs.Sub(content, "frontend/build/static"))
    router.StaticFileFS("/", "frontend/build/index.html", http.FS(content))
    

    With this setup the frontend does need to be part of your Go build step, but now it is fully contained in the binary and does not need to be separately copied into the final image.