Skip to content

Instantly share code, notes, and snippets.

@fabienjuif
Created December 24, 2025 08:34
Show Gist options
  • Select an option

  • Save fabienjuif/d19836534bd75e53bd83278320339418 to your computer and use it in GitHub Desktop.

Select an option

Save fabienjuif/d19836534bd75e53bd83278320339418 to your computer and use it in GitHub Desktop.
Vite / Runtime variables
ARG WEB_TAG=yourrepo/web:latest
# front assets
# we have to do this "from" as a workaround because --from does not support build args
FROM ${WEB_TAG} AS web_build
# generate-env image
FROM fabienjuif/generate-env:latest AS generate_env
FROM nginx:1.29.2
EXPOSE 80
EXPOSE 443
COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
COPY --from=generate_env /generate-env /usr/local/bin/generate-env
COPY --from=web_build /usr/src/app/apps/web/dist /usr/share/nginx/html
#!/usr/bin/env bash
set -e
if [ ! "$NGINX_PORT" ]; then
echo "NGINX_PORT is not set"
exit 1
fi
if [ ! "$PUBLIC_URL_BACKEND" ]; then
echo "PUBLIC_URL_BACKEND is not set"
exit 1
fi
# pinging the API once and forget
# goal is to wake it up if it's sleeping (cold start)
curl -m 5 -s "${PUBLIC_URL_BACKEND}/_/ping" &
# building the nginx config file
export NGINX_PORT
sed -i "s|%%NGINX_PORT%%|${NGINX_PORT}|g" /etc/nginx/conf.d/default.conf
export PUBLIC_URL_BACKEND
sed -i "s|%%PUBLIC_URL_BACKEND%%|${PUBLIC_URL_BACKEND}|g" /etc/nginx/conf.d/default.conf
# injecting environment variables into a JS file
echo "Generating env.js based on environment variables"
generate-env /usr/share/nginx/html
# generate robots.txt based on environment
if [ "$ENV" = "staging" ]; then
echo "Generating robots.txt for staging (Disallow: /)"
cat > /usr/share/nginx/html/robots.txt << 'EOF'
User-agent: *
Disallow: /
EOF
else
echo "Using production robots.txt from build"
fi
# starting nginx
nginx -g "daemon off;"
package main
import (
"crypto/sha256"
"fmt"
"os"
"path/filepath"
"strings"
)
// TODO: inject into index.html
var (
// Prefix for environment variables to be included in the runtime env file loaded in the browser.
prefix = "VITE_"
// List of environment variables to include in the runtime env file loaded in the browser even if they don't have the prefix.
whitelist = []string{"NODE_ENV", "PUBLIC_URL"}
// Name of the output file without the hash part
outputFileName = "env.js"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: generate-env path")
return
}
p := os.Args[1]
absPath, err := filepath.Abs(p)
if err != nil {
fmt.Println("Error getting absolute path:", err)
return
}
info, err := os.Stat(absPath)
if err != nil {
fmt.Println("Error stating path:", err)
return
}
if !info.IsDir() {
fmt.Println("Provided path is not a directory")
return
}
// create content for the env file
content := ""
content += "/* This file is generated by generate-env tool */\n\n"
content += "globalThis.import_meta_env = {};\n"
for _, e := range os.Environ() {
pair := strings.Split(e, "=")
key := pair[0]
value := pair[1]
if hasPrefix(key, prefix) || isInWhitelist(key, whitelist) {
content += fmt.Sprintf("globalThis.import_meta_env.%s = \"%s\";\n", key, value)
}
}
// hash the content so it can be cached properly
h := sha256.New()
h.Write([]byte(content))
hash := fmt.Sprintf("%x", h.Sum(nil))[:8]
// write content to the output file
outputFileName = fmt.Sprintf("env.%s.js", hash)
err = os.WriteFile(filepath.Join(absPath, outputFileName), []byte(content), 0644)
if err != nil {
fmt.Println("Error clearing file:", err)
return
}
fmt.Println("Environment file generated at:", filepath.Join(absPath, outputFileName))
// find the index.html file and update the script tag
indexFilePath := filepath.Join(absPath, "index.html")
indexContentBytes, err := os.ReadFile(indexFilePath)
if err != nil {
fmt.Println("Error reading index.html:", err)
return
}
indexContent := string(indexContentBytes)
// remove old env script tag if exists
lines := strings.Split(indexContent, "\n")
newLines := []string{}
for _, line := range lines {
if strings.Contains(line, "env.") && strings.Contains(line, ".js") {
continue
}
newLines = append(newLines, line)
}
// add new env script tag in the head, first position
scriptTag := fmt.Sprintf(` <script src="/%s"></script>`, outputFileName)
inserted := false
for i, line := range newLines {
if strings.Contains(line, "<head>") {
newLines = append(newLines[:i+1], append([]string{scriptTag}, newLines[i+1:]...)...)
inserted = true
break
}
}
if !inserted {
newLines = append([]string{scriptTag}, newLines...)
}
updatedIndexContent := strings.Join(newLines, "\n")
err = os.WriteFile(indexFilePath, []byte(updatedIndexContent), 0644)
if err != nil {
fmt.Println("Error writing updated index.html:", err)
return
}
fmt.Println("index.html updated with new env script tag.")
}
func hasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
func isInWhitelist(s string, whitelist []string) bool {
for _, w := range whitelist {
if s == w {
return true
}
}
return false
}
import unpluginEnv from "@import-meta-env/unplugin";
import { defineConfig } from "vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [
unpluginEnv.vite({
env: ".env.local",
example: ".env.local",
}),
],
server: {
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
proxy: {
"/api": {
target: process.env.API_URL ?? "http://localhost:8080",
changeOrigin: true,
},
},
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment