N
Nuxt2mo ago
NiceCraftz

Cannot parse environment for runtimeConfig

Hello everyone, i am currently build a nuxt webapp, i am trying to dockerize it but i am finding errors almost everywere i go, currently i have a "apiBaseUrl" in my runtimeConfig (not public) and have overridden it using "NUXT_API_BASE_URL" in my environment section in my Docker Compose file. as i am seeing tho while running the app, it seems that it prints out as "undefined", i don't know why :( and don't know any way to debug this, any help is really appreciated, thanks!
import tailwindcss from "@tailwindcss/vite";

export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: process.env.NODE_ENV !== "production" },
modules: ["@nuxt/eslint", "@nuxt/ui", "@pinia/nuxt"],
css: ["@/assets/css/main.css"],
runtimeConfig: {
apiBaseUrl: "http://localhost:8080",
},
vite: {
plugins: [tailwindcss()],
},
});
import tailwindcss from "@tailwindcss/vite";

export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: process.env.NODE_ENV !== "production" },
modules: ["@nuxt/eslint", "@nuxt/ui", "@pinia/nuxt"],
css: ["@/assets/css/main.css"],
runtimeConfig: {
apiBaseUrl: "http://localhost:8080",
},
vite: {
plugins: [tailwindcss()],
},
});
volumes:
pgdata:

networks:
app-network:
driver: bridge

services:
postgres:
image: postgres:17
container_name: vdspostgres
environment:
POSTGRES_DB: vdsdb
POSTGRES_USER: vdsuser
POSTGRES_PASSWORD: "VdsProject99.25!"
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- app-network

backend:
build:
context: ./vdsproduction
dockerfile: Dockerfile
container_name: vdsproduction
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/vdsdb
SPRING_DATASOURCE_USERNAME: vdsuser
SPRING_DATASOURCE_PASSWORD: VdsProject99.25!
ADMIN_TOKEN: VdsAdminFox.!25@
ASSEMBLY_TOKEN: AssemblyFoxVds!2025@
depends_on:
- postgres
networks:
- app-network

frontend:
build:
context: ./vdsfactoryviewer
dockerfile: Dockerfile
container_name: vdsviewer
ports:
- "3000:3000"
environment:
NUXT_API_BASE_URL: "http://backend:8080"
depends_on:
- backend
networks:
- app-network
volumes:
pgdata:

networks:
app-network:
driver: bridge

services:
postgres:
image: postgres:17
container_name: vdspostgres
environment:
POSTGRES_DB: vdsdb
POSTGRES_USER: vdsuser
POSTGRES_PASSWORD: "VdsProject99.25!"
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- app-network

backend:
build:
context: ./vdsproduction
dockerfile: Dockerfile
container_name: vdsproduction
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/vdsdb
SPRING_DATASOURCE_USERNAME: vdsuser
SPRING_DATASOURCE_PASSWORD: VdsProject99.25!
ADMIN_TOKEN: VdsAdminFox.!25@
ASSEMBLY_TOKEN: AssemblyFoxVds!2025@
depends_on:
- postgres
networks:
- app-network

frontend:
build:
context: ./vdsfactoryviewer
dockerfile: Dockerfile
container_name: vdsviewer
ports:
- "3000:3000"
environment:
NUXT_API_BASE_URL: "http://backend:8080"
depends_on:
- backend
networks:
- app-network
18 Replies
kapa.ai
kapa.ai2mo ago
Hi @NiceCraftz. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Your issue is that the environment variable NUXT_API_BASE_URL is not overriding your runtimeConfig.apiBaseUrl as expected in your Dockerized Nuxt app. Here’s what’s happening and how to fix it: 1. Environment Variable Naming and Mapping Nuxt only overrides runtime config values with environment variables if the variable name matches the config key, uppercased, prefixed with NUXT_, and using underscores for camelCase boundaries. For your config:
runtimeConfig: {
apiBaseUrl: "<http://localhost:8080",>
}
runtimeConfig: {
apiBaseUrl: "<http://localhost:8080",>
}
The correct environment variable should be:
NUXT_API_BASE_URL
NUXT_API_BASE_URL
—which you are already using in your Docker Compose file. 2. Accessing the Value When you access the runtime config in your app, use:
const config = useRuntimeConfig()
console.log(config.apiBaseUrl)
const config = useRuntimeConfig()
console.log(config.apiBaseUrl)
3. Debugging in Docker Important: In production (when running the built app), Nuxt does NOT read from .env files. It only uses environment variables set in the environment (like in your Docker Compose environment: section) see docs. 4. Common Pitfalls - Key must be defined in runtimeConfig: Only keys defined in runtimeConfig in your nuxt.config can be overridden. - No process.env in runtimeConfig for runtime overrides: Setting apiBaseUrl: process.env.NUXT_API_BASE_URL only works at build time, not at runtime. You should set a default value (like an empty string or your local URL), and let Nuxt override it at runtime see docs. - Check for typos: Make sure the key in runtimeConfig matches the environment variable name (case and underscores). 5. Example nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
apiBaseUrl: '', // default, will be overridden by NUXT_API_BASE_URL
},
})
export default defineNuxtConfig({
runtimeConfig: {
apiBaseUrl: '', // default, will be overridden by NUXT_API_BASE_URL
},
})
docker-compose.yaml:
frontend:
# ...
environment:
NUXT_API_BASE_URL: "<http://backend:8080">
frontend:
# ...
environment:
NUXT_API_BASE_URL: "<http://backend:8080">
Access in your app:
const config = useRuntimeConfig()
console.log(config.apiBaseUrl)
const config = useRuntimeConfig()
console.log(config.apiBaseUrl)
6. Additional Debugging - Print all environment variables in your container to ensure NUXT_API_BASE_URL is set. - Print useRuntimeConfig() in your app to see what’s available. 7. Reference - Nuxt 3 Runtime Config: Environment Variables - Nuxt 3 Runtime Config: Example --- Summary:
Your setup is correct, but ensure the key is present in runtimeConfig and you are not using process.env for runtime overrides. Nuxt will automatically pick up NUXT_API_BASE_URL at runtime if set in the environment and the key is defined in runtimeConfig see docs. If it’s still undefined, double-check your Docker environment and that you’re not running in a way that ignores the environment variable. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@689519446578298982> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
NiceCraftz
NiceCraftzOP2mo ago
no, as you can see i have set runtimeConfig: { apiBaseUrl: "http://localhost:8080", }, and yes i am using the env NUXT_API_BASE_URL in my docker compose as i already sent the problem is that when i call for example my pinia store
async createProduct(payload: ProductCreate, token: string) {
const { apiBaseUrl } = useRuntimeConfig();
const productCode = payload.code.trim();
const duplicate = this.products.find(
(product) => product.code.toLowerCase() === productCode.toLowerCase()
);
if (duplicate) {
return {
success: false,
error: `Il codice prodotto "${productCode}" esiste già.`,
};
}

const { data, error } = await useFetch<Product>(
`${apiBaseUrl}/api/v1/product/`,
{
method: "POST",
body: JSON.stringify(payload),
headers: {
Authorization: token,
},
}
);

if (data.value) {
this.products.push(data.value);
return { success: true, product: data.value };
}

if (error.value) {
return {
success: false,
error: error.value.data?.message || "Errore sconosciuto",
};
}

return { success: false, error: "Errore sconosciuto" };
},
async createProduct(payload: ProductCreate, token: string) {
const { apiBaseUrl } = useRuntimeConfig();
const productCode = payload.code.trim();
const duplicate = this.products.find(
(product) => product.code.toLowerCase() === productCode.toLowerCase()
);
if (duplicate) {
return {
success: false,
error: `Il codice prodotto "${productCode}" esiste già.`,
};
}

const { data, error } = await useFetch<Product>(
`${apiBaseUrl}/api/v1/product/`,
{
method: "POST",
body: JSON.stringify(payload),
headers: {
Authorization: token,
},
}
);

if (data.value) {
this.products.push(data.value);
return { success: true, product: data.value };
}

if (error.value) {
return {
success: false,
error: error.value.data?.message || "Errore sconosciuto",
};
}

return { success: false, error: "Errore sconosciuto" };
},
the apiBaseUrl is undefined. also yes, i need to query my data from the server because i do not want to expose my api endpoints to the public. seems like the get is passing and the other operations such as post aren't vdsproduction | 2025-10-30T16:09:51.653+01:00 INFO 1 --- [vdsproduction] [nio-8080-exec-1] i.f.v.controller.ProductionController : Get all productions vdsproduction | 2025-10-30T16:09:51.817+01:00 INFO 1 --- [vdsproduction] [nio-8080-exec-2] i.f.v.controller.ProductController : Getting all products vdsviewer | [Vue Router warn]: No match found for location with path "/undefined/api/v1/product/" also i have currently tried to remake my api access system to use this composable:
export const useApiHelper = () => {
function get<T>(endpoint: string) {
const { apiBaseUrl } = useRuntimeConfig();
console.log("API Base URL:", apiBaseUrl);

return $fetch<T>(`${apiBaseUrl}${endpoint}`);
}

function post<T, D>(endpoint: string, body: D, token?: string) {
const { apiBaseUrl } = useRuntimeConfig();
console.log("API Base URL:", apiBaseUrl);

const headers = token ? { Authorization: token } : undefined;
const isFormData = body instanceof FormData;

return $fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "POST" as const,
body: isFormData ? body : JSON.stringify(body),
headers,
});
}

function patch<T, D>(endpoint: string, body: D, token?: string) {
const { apiBaseUrl } = useRuntimeConfig();
console.log("API Base URL:", apiBaseUrl);

const headers = token ? { Authorization: token } : undefined;
return $fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "PATCH",
body: JSON.stringify(body),
headers,
});
}

return { get, post, patch };
};
export const useApiHelper = () => {
function get<T>(endpoint: string) {
const { apiBaseUrl } = useRuntimeConfig();
console.log("API Base URL:", apiBaseUrl);

return $fetch<T>(`${apiBaseUrl}${endpoint}`);
}

function post<T, D>(endpoint: string, body: D, token?: string) {
const { apiBaseUrl } = useRuntimeConfig();
console.log("API Base URL:", apiBaseUrl);

const headers = token ? { Authorization: token } : undefined;
const isFormData = body instanceof FormData;

return $fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "POST" as const,
body: isFormData ? body : JSON.stringify(body),
headers,
});
}

function patch<T, D>(endpoint: string, body: D, token?: string) {
const { apiBaseUrl } = useRuntimeConfig();
console.log("API Base URL:", apiBaseUrl);

const headers = token ? { Authorization: token } : undefined;
return $fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "PATCH",
body: JSON.stringify(body),
headers,
});
}

return { get, post, patch };
};
Quantix
Quantix2mo ago
move your useRuntimeConfig call outside of your functions get post and patch and have them at the top-level of useApiHelper composables should not be called outside of the vue/nuxt context
Quantix
Quantix2mo ago
Good video from Alex around composables https://www.youtube.com/watch?v=N0QrFKBZuqA
Alexander Lichter
YouTube
Is your function REALLY a Vue composable?
💚 Vue's Composition API is amazing and my preferred way to write components nowadays. But often I see that developers don't know when functions are Vue composables, and when they are plain functions. Do you know? In this video we will cover exactly this: 🧠 Explaining what a Vue composable actually is ✅ Going through a checklist to figur...
NiceCraftz
NiceCraftzOP2mo ago
still doesn't work still, its undefined in some cases Listening on http://[::]:3000 API Base URL: http://localhost:8080 API Base URL: http://localhost:8080 [Vue Router warn]: No match found for location with path "/undefined/api/v1/product/" [Vue Router warn]: No match found for location with path "/undefined/api/v1/product/" API Base URL: http://localhost:8080 API Base URL: http://localhost:8080
export const useApiHelper = () => {
const { apiBaseUrl } = useRuntimeConfig();

function get<T>(endpoint: string) {
console.log("API Base URL:", apiBaseUrl);
return useAsyncData<T>(() => $fetch<T>(`${apiBaseUrl}${endpoint}`));
}

function post<T, D>(endpoint: string, body: D, token?: string) {
console.log("API Base URL:", apiBaseUrl);
const headers = token ? { Authorization: token } : undefined;
const isFormData = body instanceof FormData;

return useAsyncData<T>(() =>
$fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "POST" as const,
body: isFormData ? body : JSON.stringify(body),
headers,
})
);
}

function patch<T, D>(endpoint: string, body: D, token?: string) {
console.log("API Base URL:", apiBaseUrl);
const headers = token ? { Authorization: token } : undefined;
return useAsyncData<T>(() =>
$fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "PATCH",
body: JSON.stringify(body),
headers,
})
);
}

return { get, post, patch };
};
export const useApiHelper = () => {
const { apiBaseUrl } = useRuntimeConfig();

function get<T>(endpoint: string) {
console.log("API Base URL:", apiBaseUrl);
return useAsyncData<T>(() => $fetch<T>(`${apiBaseUrl}${endpoint}`));
}

function post<T, D>(endpoint: string, body: D, token?: string) {
console.log("API Base URL:", apiBaseUrl);
const headers = token ? { Authorization: token } : undefined;
const isFormData = body instanceof FormData;

return useAsyncData<T>(() =>
$fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "POST" as const,
body: isFormData ? body : JSON.stringify(body),
headers,
})
);
}

function patch<T, D>(endpoint: string, body: D, token?: string) {
console.log("API Base URL:", apiBaseUrl);
const headers = token ? { Authorization: token } : undefined;
return useAsyncData<T>(() =>
$fetch<T>(`${apiBaseUrl}${endpoint}`, {
method: "PATCH",
body: JSON.stringify(body),
headers,
})
);
}

return { get, post, patch };
};
get works, post and patch does not :=(
Quantix
Quantix2mo ago
where you do you call useApiHelper ?
NiceCraftz
NiceCraftzOP2mo ago
inside my pinia store i am changing like big parts of my code related to store and data managment but i think i am just making a mess
Quantix
Quantix2mo ago
could you provide me with the code composables must be ran at the top in the nuxt context which is in other composables, in script setup, etc
NiceCraftz
NiceCraftzOP2mo ago
yes, let me git reset lol here you go sorry.
import { defineStore } from "pinia";
import { useApiHelper } from "~/composables/apiHelper";
import {
ProductionStatus,
type Production,
type ProductionCreate,
type ProductionEdit,
type ProductionItemAdd,
} from "~/types/production";

const { post, patch, get } = useApiHelper();

export const useProductionStore = defineStore("productionStore", {
state: () => ({
productions: [] as Production[],
loaded: false,
}),
getters: {
getProductions(state) {
return state.productions;
},
getLateProductions(state) {
const monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
return state.productions.filter((production) => {
const dueDate = new Date(production.dueDate);
return (
dueDate < monthAgo || production.state === ProductionStatus.URGENT
);
});
},
getProductionsByMonth(state) {
return (month: number) => {
return state.productions.filter((production) => {
const dueDate = new Date(production.dueDate);
return dueDate.getMonth() === month;
});
};
},
getCompletedToday(state) {
const today = new Date();
return state.productions.filter((production) => {
if (!production.completedAt) return false;
const completedAt = new Date(production.completedAt);
return (
completedAt.getDate() === today.getDate() &&
completedAt.getMonth() === today.getMonth() &&
completedAt.getFullYear() === today.getFullYear()
);
});
},
getActiveProductions(state) {
return state.productions.filter(
(production) => production.state !== ProductionStatus.COMPLETED
);
},
},
actions: {
async fetchProductions() {
if (this.loaded) return this.productions;
const { data } = await get<Production[]>("/api/v1/production/");

this.productions = data.value || [];
this.loaded = true;
},
async updateProduction(payload: ProductionEdit, token: string) {
const { data } = await patch<Production, ProductionEdit>(
"/api/v1/production/update",
payload,
token
);

const productionPatched = data.value;
if (productionPatched) {
const index = this.productions.findIndex(
(prod) => prod.id === productionPatched.id
);

if (index !== -1) this.productions[index] = productionPatched;
}
},

async addProductionItem(payload: ProductionItemAdd, token: string) {
const { data } = await post<Production, ProductionItemAdd>(
"/api/v1/production/update",
payload,
token
);

const productionItemAdded = data.value;
if (productionItemAdded) {
const index = this.productions.findIndex(
(prod) => prod.id === productionItemAdded.id
);

if (index !== -1) this.productions[index] = productionItemAdded;
}
},

async createProduction(payload: ProductionCreate, token: string) {
const { data } = await post<Production, ProductionCreate>(
"/api/v1/production/",
payload,
token
);

const productionCreated = data.value;
if (productionCreated) this.productions.push(productionCreated);
},
},
});
import { defineStore } from "pinia";
import { useApiHelper } from "~/composables/apiHelper";
import {
ProductionStatus,
type Production,
type ProductionCreate,
type ProductionEdit,
type ProductionItemAdd,
} from "~/types/production";

const { post, patch, get } = useApiHelper();

export const useProductionStore = defineStore("productionStore", {
state: () => ({
productions: [] as Production[],
loaded: false,
}),
getters: {
getProductions(state) {
return state.productions;
},
getLateProductions(state) {
const monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
return state.productions.filter((production) => {
const dueDate = new Date(production.dueDate);
return (
dueDate < monthAgo || production.state === ProductionStatus.URGENT
);
});
},
getProductionsByMonth(state) {
return (month: number) => {
return state.productions.filter((production) => {
const dueDate = new Date(production.dueDate);
return dueDate.getMonth() === month;
});
};
},
getCompletedToday(state) {
const today = new Date();
return state.productions.filter((production) => {
if (!production.completedAt) return false;
const completedAt = new Date(production.completedAt);
return (
completedAt.getDate() === today.getDate() &&
completedAt.getMonth() === today.getMonth() &&
completedAt.getFullYear() === today.getFullYear()
);
});
},
getActiveProductions(state) {
return state.productions.filter(
(production) => production.state !== ProductionStatus.COMPLETED
);
},
},
actions: {
async fetchProductions() {
if (this.loaded) return this.productions;
const { data } = await get<Production[]>("/api/v1/production/");

this.productions = data.value || [];
this.loaded = true;
},
async updateProduction(payload: ProductionEdit, token: string) {
const { data } = await patch<Production, ProductionEdit>(
"/api/v1/production/update",
payload,
token
);

const productionPatched = data.value;
if (productionPatched) {
const index = this.productions.findIndex(
(prod) => prod.id === productionPatched.id
);

if (index !== -1) this.productions[index] = productionPatched;
}
},

async addProductionItem(payload: ProductionItemAdd, token: string) {
const { data } = await post<Production, ProductionItemAdd>(
"/api/v1/production/update",
payload,
token
);

const productionItemAdded = data.value;
if (productionItemAdded) {
const index = this.productions.findIndex(
(prod) => prod.id === productionItemAdded.id
);

if (index !== -1) this.productions[index] = productionItemAdded;
}
},

async createProduction(payload: ProductionCreate, token: string) {
const { data } = await post<Production, ProductionCreate>(
"/api/v1/production/",
payload,
token
);

const productionCreated = data.value;
if (productionCreated) this.productions.push(productionCreated);
},
},
});
this is a last change, i stil haven't tested this, but before i would call const {get} = useApiHelper() and then use it in a store function, so for every function i would have a useApiHelper call which should not work because i am calling it out of the nuxt context
Quantix
Quantix2mo ago
ok so you're calling your composable at the top level which means when the module is imported that function immediately runs which also means there's no guarantee it's called within the nuxt content composables should be ran in other composables, or in vue setup check this video for more info on how composables work
NiceCraftz
NiceCraftzOP2mo ago
i did
Quantix
Quantix2mo ago
generally speaking in nuxt you should never define a constant like that cause there's always the chance of pollution
NiceCraftz
NiceCraftzOP2mo ago
im not english sorry but if i read stuff i understand better than talking i never had this problem with nuxt honestly i always deployed my apps without any problem, its the first time something like this happens i can't get an idea on why it doesn't work and why that stupid apiBaseUrl variable is undefined, but when i run the get it works fine like wtf
Quantix
Quantix2mo ago
you shouldn't call your useApiHelper composable outside of the nuxt context you're calling it at the module top level you could try
export const useProductionStore = () => {
const { post, patch, get } = useApiHelper()
return defineStore(/*... blah blah blah */)
}
export const useProductionStore = () => {
const { post, patch, get } = useApiHelper()
return defineStore(/*... blah blah blah */)
}
to see if that helps i'm not very familiar with pinia
NiceCraftz
NiceCraftzOP2mo ago
no, it doesn't work still is undefined. as i said, fetch function with get method works, everything else doesn't i don't know why i chose typescript honestly, could have done something better with my life
Quantix
Quantix2mo ago
my assumptions now are that you call useProductionStore multiple times in multiple locations if it only works for getting then the other ones must be called in an erroneous place
NiceCraftz
NiceCraftzOP2mo ago
i fixed it with claude code in the end, that boy found it like in 3 seconds, and fixed it idk how cuz he was so fast i will look after in the commit.

Did you find this page helpful?