I've set up a nixOS
to run an nginx
as a reverse proxy for a docker-container. In the docker-container runs an golang
-server which handles /
with a function and also return files from the two folders static
and js
. It runs on port 8181. The code for this looks like this:
func main() {
static := http.FileServer(http.Dir("static"))
js := http.FileServer(http.Dir("js"))
http.Handle("/static/", http.StripPrefix("/static/", static))
http.Handle("/js/", http.StripPrefix("/js/", js))
// Register function to "/"
http.HandleFunc("/", indexHandler)
fmt.Println("Server is starting...")
err := http.ListenAndServe("0.0.0.0:8181", nil)
if err != nil {
log.Fatal("cannot listen and server", err)
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
wd, err := os.Getwd()
var static = filepath.Join(wd, "static")
var index = filepath.Join(static, "index.html")
if err != nil {
log.Fatal("cannot get working directory", err)
}
// Check if the request is an OPTIONS preflight request
if r.Method == "OPTIONS" {
// Respond with status OK to preflight requests
w.WriteHeader(http.StatusOK)
return
}
// POST-Request
if r.Method == http.MethodPost {
// do something
} else {
// Loading the index.html without any data
tmpl, err := template.ParseFiles(index)
err = tmpl.Execute(w, nil) // write response to w
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Fatal("problem with parsing the index template ", err)
}
}
}
The structure of my app looks like this.
├── web
│ ├── Dockerfile
│ ├── go.mod
│ ├── server.go
│ └── static
│ │ ├── index.html
│ │ ├── style.css
│ │ └── table.html
│ └── js
│ └── htmx.min.js
The configuration of the nginx
-section in the configuration.nix
looks like this.
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts."myapp" = {
sslCertificate = "/etc/ssl/nginx/ssl.cert";
sslCertificateKey = "/etc/ssl/nginx/ssl.key";
sslTrustedCertificate = "/etc/ssl/nginx/ssl.chain";
forceSSL = true; # Redirect HTTP to HTTPS
locations = {
"/myapp" = { proxyPass = "http://localhost:8181/"; };
};
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
'';
};
};
When I address the url https://server/myapp
the index.html
will load, but the style.css
and the htmx.min.js
will return a 404.
When addressing the container with the port 8181 instead of the url (http://server:8181
), everything loads as usual.
I expect nginx to redirect requests for loading css and js to the container.
EDIT:
The exact error is: GET https://server.name/static/style.css net::ERR_ABORTED 404
even though I am accessing https://server.name/myapp
.
Another important information is that, I want run multiple containers this way over the same reverse proxy. So directing als css
- or js
-files to the same location won't work.
Please check the css/js references in the index.html
file. It's very likely that you're referencing them by absolute paths like this:
<html>
<head>
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<script src="/js/htmx.min.js"></script>
</head>
</html>
A quick fix is to replace them with relative paths:
<html>
<head>
<link rel="stylesheet" type="text/css" href="./static/style.css" />
<script src="./js/htmx.min.js"></script>
</head>
</html>
This approach is error-prone. A better approach is to modify the app to serve the content at /myapp
:
http.Handle("/myapp/static/", http.StripPrefix("/myapp/static/", static))
http.Handle("/myapp/js/", http.StripPrefix("/myapp/js/", js))
http.HandleFunc("/myapp/", indexHandler)
And modify the reference paths in the index.html
file:
<html>
<head>
<link rel="stylesheet" type="text/css" href="/myapp/static/style.css" />
<script src="/myapp/js/htmx.min.js"></script>
</head>
</html>
And then modify the nginx configuration:
locations = {
"/myapp/" = { proxyPass = "http://localhost:8181/myapp/"; };
};
With this approach, no matter the reverse proxy is used or not, the web app will always be served at the uri /myapp/
, which should make it easy to maintain.