From 4661bae48d93253a25d3d3eccc40ac7f0348773e Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Sat, 29 Feb 2020 20:06:18 -0500 Subject: [PATCH] One dockerfile to rule them all --- Dockerfile | 34 ++++++++++++++++++++ api/Dockerfile | 9 ------ api/server.go | 55 ++++++++++++++++++++++++++++++--- docker-compose.example.yml | 36 +++------------------ docker/nginx-proxy/default.conf | 36 --------------------- ui/Dockerfile | 22 ------------- ui/docker-nginx.conf | 9 ------ ui/package.json | 6 +--- 8 files changed, 91 insertions(+), 116 deletions(-) create mode 100644 Dockerfile delete mode 100644 api/Dockerfile delete mode 100644 docker/nginx-proxy/default.conf delete mode 100644 ui/Dockerfile delete mode 100644 ui/docker-nginx.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..712ac816 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# Build UI +FROM node:10 as ui + +ARG GRAPHQL_ENDPOINT +ENV GRAPHQL_ENDPOINT=${GRAPHQL_ENDPOINT} + +RUN mkdir -p /app +WORKDIR /app + +COPY ui/package*.json ./ +RUN npm install +COPY ui /app + +RUN npm run build + +# Build API +FROM golang:alpine AS api + +WORKDIR /app +COPY api /app + +RUN go get -d -v ./... +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o photoview . + +# Copy built app to nginx environment +FROM alpine:latest + +COPY --from=ui /app/dist /ui +COPY --from=api /app/database/migrations /database/migrations +COPY --from=api /app/photoview /app/photoview + +EXPOSE 80 + +ENTRYPOINT ["/app/photoview"] diff --git a/api/Dockerfile b/api/Dockerfile deleted file mode 100644 index f9e4db83..00000000 --- a/api/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM golang:latest - -WORKDIR /app -COPY . . - -RUN go get -d -v ./... -RUN go build -o photoview . - -CMD ["/app/photoview"] \ No newline at end of file diff --git a/api/server.go b/api/server.go index 64938365..788f7be2 100644 --- a/api/server.go +++ b/api/server.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "path" + "path/filepath" "github.com/gorilla/mux" @@ -23,6 +24,49 @@ import ( const defaultPort = "4001" +// spaHandler implements the http.Handler interface, so we can use it +// to respond to HTTP requests. The path to the static directory and +// path to the index file within that static directory are used to +// serve the SPA in the given static directory. +type spaHandler struct { + staticPath string + indexPath string +} + +// ServeHTTP inspects the URL path to locate a file within the static dir +// on the SPA handler. If a file is found, it will be served. If not, the +// file located at the index path on the SPA handler will be served. This +// is suitable behavior for serving an SPA (single page application). +func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // get the absolute path to prevent directory traversal + path, err := filepath.Abs(r.URL.Path) + if err != nil { + // if we failed to get the absolute path respond with a 400 bad request + // and stop + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // prepend the path with the path to the static directory + path = filepath.Join(h.staticPath, path) + + // check whether a file exists at the given path + _, err = os.Stat(path) + if os.IsNotExist(err) { + // file does not exist, serve index.html + http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) + return + } else if err != nil { + // if we got an error (that wasn't that the file doesn't exist) stating the + // file, return a 500 internal server error and stop + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // otherwise, use http.FileServer to serve the static dir + http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) +} + func main() { if err := godotenv.Load(); err != nil { @@ -68,14 +112,14 @@ func main() { endpointRouter := rootRouter.PathPrefix(endpointURL.Path).Subrouter() if devMode { - endpointRouter.Handle("/", handler.Playground("GraphQL playground", path.Join(endpointURL.Path, "/graphql"))) + endpointRouter.Handle("/api", handler.Playground("GraphQL playground", path.Join(endpointURL.Path, "/graphql"))) } else { - endpointRouter.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + endpointRouter.HandleFunc("/api", func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("photoview api endpoint")) }) } - endpointRouter.Handle("/graphql", + endpointRouter.Handle("/api/graphql", handler.GraphQL(photoview_graphql.NewExecutableSchema(graphqlConfig), handler.IntrospectionEnabled(devMode), handler.WebsocketUpgrader(server.WebsocketUpgrader(devMode)), @@ -83,9 +127,12 @@ func main() { ), ) - photoRouter := endpointRouter.PathPrefix("/photo").Subrouter() + photoRouter := endpointRouter.PathPrefix("/api/photo").Subrouter() routes.RegisterPhotoRoutes(db, photoRouter) + spa := spaHandler{staticPath: "/ui", indexPath: "index.html"} + endpointRouter.PathPrefix("/").Handler(spa) + if devMode { log.Printf("🚀 Graphql playground ready at %s", endpointURL.String()) } else { diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 107d9ae6..72bd31f4 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -12,11 +12,11 @@ services: volumes: - db_data:/var/lib/mysql - api: - build: ./api + photoview: + build: . restart: always - expose: - - 80 + ports: + - "8000:80" depends_on: - db environment: @@ -25,7 +25,7 @@ services: # Change This: The publicly exposed url for the api # For example if the server is available from the domain example.com, # change this value to http://example.com/api - - API_ENDPOINT=http://localhost:8080/api + - API_ENDPOINT=http://localhost:8080/ - PUBLIC_ENDPOINT=http://localhost:8080/ - API_LISTEN_PORT=80 volumes: @@ -37,32 +37,6 @@ services: - ./photos_path:/photos:ro - api_cache:/app/cache - ui: - build: - context: ./ui - args: - # Change This: The publicly exposed url for the graphql api - # For example if the server is available from the domain example.com, - # change this value to http://example.com/api/graphql - GRAPHQL_ENDPOINT: http://localhost:8080/api/graphql - restart: always - expose: - - 80 - depends_on: - - api - - proxy: - image: nginx - restart: always - volumes: - - ./docker/nginx-proxy/default.conf:/etc/nginx/conf.d/default.conf - ports: - # Change This: Replace 8080 with the port you want photoview to be accessible at - - 8080:80 - depends_on: - - api - - ui - volumes: db_data: api_cache: diff --git a/docker/nginx-proxy/default.conf b/docker/nginx-proxy/default.conf deleted file mode 100644 index 37b35712..00000000 --- a/docker/nginx-proxy/default.conf +++ /dev/null @@ -1,36 +0,0 @@ -server { - listen 80 default_server; - - location /api { - proxy_pass http://api; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Required for Websocket - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; - client_max_body_size 0; - - access_log /var/log/nginx/photoview-api.access.log; - error_log /var/log/nginx/photoview-api.error.log; - } - - location / { - proxy_pass http://ui; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; - client_max_body_size 0; - - access_log /var/log/nginx/photoview-ui.access.log; - error_log /var/log/nginx/photoview-ui.error.log; - } - -} \ No newline at end of file diff --git a/ui/Dockerfile b/ui/Dockerfile deleted file mode 100644 index bf6b65f8..00000000 --- a/ui/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# Build the app -FROM node:10 - -ARG GRAPHQL_ENDPOINT -ENV GRAPHQL_ENDPOINT=${GRAPHQL_ENDPOINT} - -RUN mkdir -p /app -WORKDIR /app - -COPY package*.json ./ -RUN npm install -COPY . . - -RUN npm run build - -# Copy built app to nginx environment -FROM nginx:stable - -COPY --from=0 /app/dist /usr/share/nginx/html -COPY ./docker-nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 \ No newline at end of file diff --git a/ui/docker-nginx.conf b/ui/docker-nginx.conf deleted file mode 100644 index 2ff0b308..00000000 --- a/ui/docker-nginx.conf +++ /dev/null @@ -1,9 +0,0 @@ -server { - listen 80 default_server; - - root /usr/share/nginx/html; - - location / { - try_files $uri /index.html; - } -} \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index b4e09ca0..d05fd361 100644 --- a/ui/package.json +++ b/ui/package.json @@ -53,11 +53,7 @@ "react-router-prop-types": "^1.0.4" }, "cache": { - "swDest": "service-worker.js", - "modifyURLPrefix": { - "/": "/ui", - "": "/ui/" - } + "swDest": "service-worker.js" }, "husky": { "hooks": {