Add Docker support with runtime environment variable configuration

- Remove temporary arne.js file
- Add runtime configuration via window.__RUNTIME_CONFIG__
- Create public/config.template.js for hostname injection
- Add Dockerfile with multi-stage build (node + nginx)
- Add docker-entrypoint.sh to generate config.js at startup
- Add .dockerignore
- Update all components to use runtime config instead of arne.js
- Update index.html to load config.js before app

This allows deploying the same Docker image to different environments
by setting the API_HOSTNAME environment variable at runtime.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-05-30 22:25:59 +02:00
parent 2d7c620c6a
commit bc32ce5fc5
9 changed files with 44 additions and 16 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules
.git
dist
.DS_Store
+18
View File
@@ -0,0 +1,18 @@
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Runtime stage
FROM nginx:alpine
RUN apk add --no-cache gettext
COPY --from=builder /app/dist /usr/share/nginx/html
COPY public/config.template.js /usr/share/nginx/html/
COPY docker-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
set -e
# Generate config.js from template at runtime
envsubst < /usr/share/nginx/html/config.template.js > /usr/share/nginx/html/config.js
# Start nginx
exec "$@"
+3
View File
@@ -0,0 +1,3 @@
window.__RUNTIME_CONFIG__ = {
API_HOSTNAME: '${API_HOSTNAME}'
};
+1
View File
@@ -8,6 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
<script src="<%= BASE_URL %>config.js"></script>
</head> </head>
<body> <body>
<noscript> <noscript>
-3
View File
@@ -1,3 +0,0 @@
export default {
hostname: "$HOSTNAME",
}
+1 -2
View File
@@ -28,7 +28,6 @@
</template> </template>
<script> <script>
import arne from '../../arne.js'
import {mapState} from "vuex"; import {mapState} from "vuex";
export default { export default {
data() { data() {
@@ -93,7 +92,7 @@ export default {
reloadGames() { reloadGames() {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/all`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/all`,
}) })
.then((response) => { .then((response) => {
this.allGamesList = response.data; this.allGamesList = response.data;
+2 -3
View File
@@ -8,7 +8,6 @@
</template> </template>
<script> <script>
import arne from '../../arne.js'
export default { export default {
data() { data() {
return { return {
@@ -47,7 +46,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/reset`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/reset`,
}) })
.then(() => { .then(() => {
resolve(); resolve();
@@ -62,7 +61,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/sync`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/sync`,
}) })
.then(() => { .then(() => {
resolve(); resolve();
+7 -8
View File
@@ -28,7 +28,6 @@
<script> <script>
import { mapState } from "vuex"; import { mapState } from "vuex";
import arne from "../../arne.js";
export default { export default {
data() { data() {
return { return {
@@ -168,7 +167,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/rand`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/rand`,
responseType: "blob", responseType: "blob",
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
let percentCompleted = Math.round( let percentCompleted = Math.round(
@@ -198,7 +197,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/rand/low`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/rand/low`,
responseType: "blob", responseType: "blob",
onDownloadProgress: (progressEvent) => { onDownloadProgress: (progressEvent) => {
let percentCompleted = Math.round( let percentCompleted = Math.round(
@@ -228,7 +227,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/addPlayed`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/addPlayed`,
}) })
.then(() => { .then(() => {
resolve(false); resolve(false);
@@ -243,7 +242,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/addQue`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/addQue`,
}) })
.then(() => { .then(() => {
resolve(false); resolve(false);
@@ -258,7 +257,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/info`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/info`,
}) })
.then((response) => { .then((response) => {
let gameInfoObject = { let gameInfoObject = {
@@ -280,7 +279,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music/list`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music/list`,
}) })
.then((response) => { .then((response) => {
let tracklistArray = response.data; let tracklistArray = response.data;
@@ -297,7 +296,7 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axios({ this.axios({
method: "get", method: "get",
url: `${arne.hostname}/music?song=${trackNumber}`, url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/music?song=${trackNumber}`,
responseType: "blob", responseType: "blob",
}) })
.then((response) => { .then((response) => {