Vue Storefront is now Alokai! Learn More
Migration to Runtime ENVs & Docker cached building process

Migration to Runtime ENVs & Docker cached building process

Reasons & Benefits

Version Sprint 22 of the Storefront contains frontend applications that using environment variables for configuration purposes. The way how they are used makes that values of those variables are hard-coded into the code of the frontend applications.

During the Middleware application is fully configurable using the environment variables via Alokai Console, the frontend apps require a few of modifications to change the value of environment variables. To reduce those differences, we introduced changes in our modules for Next & Nuxt storefronts, to allow use environment variables in runtime.

This brings us benefits in simplicity of making changes into configuration of Storefront, remove the need of rebuild and redeploy after every change.

At the same time, we introduce the caching Docker build process in our deployment actions. Both of these changes are interconnected with each other, that's why we present them in a single migration guide.

Next.js (Pages Router)

  1. Update @vue-storefront/next package to version 3.0.0 and install next-runtime-env as dependency
cd apps/storefront-unified-nextjs
yarn add next-runtime-env@1.7.4 @vue-storefront/next@3.0.1 @vue-storefront/sdk@3.1.0
  1. Update Next config, placed in apps/storefront-unified-nextjs/next.config.js
// Imports on the begining of the file
const { configureRuntimeEnv } = require('next-runtime-env/build/configure'); const { env } = require('next-runtime-env'); 
configureRuntimeEnv(); 
// ... rest of the config
const withPwa = require('next-pwa')({
// ...
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  // ...
  images: env('NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL') ? cloudinaryConfig : defaultImageConfig,   // ...
  async headers() {
    return [
      {
        source: '/__ENV.js',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=0, s-max-age=15',
          },
        ],
      },
    ];
  },
};
  1. Update the environment variables

For local development, update the .env file:

# apps/storefront-unified-nextjs/.env
NEXT_PUBLIC_API_BASE_URL="http://localhost:4000"NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL="http://localhost:4000"NEXT_PUBLIC_MULTISTORE_ENABLED=falseNEXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED=false# For CDN cache busting, you can use a hash or a version number. By default, deployed version
# uses the git commit hash. For local development, you can use a random string or skip it.
NEXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID="example-hash"# Default Image Loader fetch url.
# For Cloudinary check https://cloudinary.com/documentation/fetch_remote_images#fetch_and_deliver_remote_files
NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL=https://res.cloudinary.com/dcqchkrzw/image/fetch/
# Optional. Will be used when image url will not start with http.
# For Cloudinary check https://cloudinary.com/documentation/migration#lazy_migration_with_auto_upload
NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL=https://res.cloudinary.com/vsf-sap/image/upload/
NEXT_DEFAULT_HTML_CACHE_CONTROL="public, max-age=0, s-maxage=15, must-revalidate"

You can manage environment variables for your deployed project in the Console. Go to the Settings -> Environment variables -> Section Storefront Application. Click on the "Add variable", and use the modal fill the name & value of each environment variable. Repeat for all the variables needed to run the app.

  1. Update _document.tsx in the apps/storefront-unified-nextjs/pages/
/* eslint-disable @next/next/no-sync-scripts */
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <meta
            httpEquiv="delegate-ch"
            content="sec-ch-width https://res.cloudinary.com; sec-ch-dpr https://res.cloudinary.com; sec-ch-viewport-width https://res.cloudinary.com;"
          />
          <meta name="theme-color" content="#018937" />
          <link rel="apple-touch-icon" href="/icons/apple-touch-icon-180x180.png" />
          <link rel="manifest" href="/manifest.json" />
          <link rel="preconnect" href="https://res.cloudinary.com/" crossOrigin="anonymous" />
          <link rel="dns-prefetch" href="https://res.cloudinary.com/" />
          <script src="/__ENV.js" /> {/* Allows for passing runtime environment variables to the browser */}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;
  1. Update SSR cache control
// apps/storefront-unified-nextjs/helpers/ssr/setCacheControl.ts

import type { IncomingMessage, ServerResponse } from 'http';
import { env } from 'next-runtime-env'; 
export function setCacheControl(res: ServerResponse<IncomingMessage>, custom?: string) {
  // WARNING: it doesn't work in dev mode, since Next disables caching
  // while running the app in this mode.
  const cacheControlSettings = custom ?? process.env.NEXT_DEFAULT_HTML_CACHE_CONTROL;   const cacheControlSettings = custom ?? env('NEXT_DEFAULT_HTML_CACHE_CONTROL');   if (!cacheControlSettings) return;
  res.setHeader('Cache-Control', cacheControlSettings);
  res.setHeader('Vary', 'Accept-Encoding');
}
  1. Update Cloudinary config
// apps/storefront-unified-nextjs/config/image-loaders/cloudinary/cloudinary.ts

import { NextImageLoader } from './types';
import { env } from 'next-runtime-env'; 
const cloudinaryLoader: NextImageLoader = ({ src, width, quality }) => {
  if (!process.env.NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL) {   if (!env('NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL')) {     // eslint-disable-next-line no-console
    console.warn(`NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL is not defined, skipping Cloudinary image optimization.`);
    return src;
  }

  let baseURL = process.env.NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL;   let baseURL = env('NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL'); 
  if (!src.startsWith('http') && process.env.NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL) {   if (!src.startsWith('http') && env('NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL')) {     baseURL = process.env.NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL;     baseURL = env('NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL');   }

  const params = ['f_auto', 'c_limit', 'dpr_auto', `w_${width}`, `q_${quality || 'auto'}`];

  return `${baseURL}${params.join(',')}/${src}`;
};

export default cloudinaryLoader;
  1. Update .gitignore in the NextJS app. Add this line at the bottom of the file:
# apps/storefront-unified-nextjs/.gitignore
public/__ENV.js
  1. Update the SDK configuration

Let's start with updating a sdk/sdk.config.ts file:

import { env } from 'next-runtime-env'; import { contentfulModule } from '@vsf-enterprise/contentful-sdk';
import { CreateSdkOptions, createSdk } from '@vue-storefront/next';
import type { UnifiedEndpoints, CheckoutEndpoints } from 'storefront-middleware/types';

if (!process.env.NEXT_PUBLIC_API_BASE_URL) {
  throw new Error('NEXT_PUBLIC_API_BASE_URL is required to run the app');
}

const options: CreateSdkOptions = {
  middleware: {
    apiUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
  },

  multistore: {
    enabled: process.env.NEXT_PUBLIC_MULTISTORE_ENABLED === 'true',
  },
};

const apiUrl = env('NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL') ?? '';
const ssrApiUrl = env('NEXT_PUBLIC_ALOKAI_MIDDLEWARE_SSR_API_URL');
const cdnCacheBustingId =
  env('NEXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID') ?? 'no-cache-busting-id-set';
const isMultiStoreEnabled = env('NEXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED') === 'true';
if (!apiUrl) {
  throw new Error('NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL is required to run the app');
}

const options: CreateSdkOptions = {
  middleware: {
    apiUrl,
    cdnCacheBustingId,
    ssrApiUrl,
  },
  multistore: {
    enabled: isMultiStoreEnabled,
  },
};

export const { getSdk } = createSdk(options, ({ buildModule, middlewareModule, middlewareUrl, getRequestHeaders }) => ({ export const { getSdk } = createSdk(options, ({ buildModule, middlewareModule, config, getRequestHeaders }) => ({   unified: buildModule(middlewareModule<UnifiedEndpoints>, {
    apiUrl: `${middlewareUrl}/commerce`,     apiUrl: `${config.middlewareUrl}/commerce`, 
    defaultRequestConfig: {
      headers: getRequestHeaders(),
    },
  }),

  contentful: buildModule(contentfulModule, {
    apiUrl: `${middlewareUrl}/cntf`,     apiUrl: `${config.middlewareUrl}/cntf`,   }),

  checkout: buildModule(middlewareModule<CheckoutEndpoints>, {
    apiUrl: `${middlewareUrl}/commerce/checkout`,     apiUrl: `${config.middlewareUrl}/commerce/checkout`, 
    defaultRequestConfig: {
      headers: getRequestHeaders(),
    },
  }),
}));

export type Sdk = ReturnType<typeof getSdk>;

The next step is to update the SDK Provider in sdk/SdkProvider.ts:

import { createSdkContext } from '@vue-storefront/next/client';
import { getSdk } from './sdk.config'; import type { Sdk } from './sdk.config'; 
export const [SdkProvider, useSdk] = createSdkContext(getSdk());  export const [SdkProvider, useSdk] = createSdkContext<Sdk>(); 
  1. As a final step, update the pages/_app.tsx file:
import { useEffect, useState } from 'react';
import { NextPage } from 'next';
import type { AppProps } from 'next/app';
import { HydrationBoundary, MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import classNames from 'classnames';
import { appWithTranslation } from 'next-i18next';
import { DefaultSeo } from 'next-seo';
import { defaultSeoConfig } from '~/config/seo';
import { AuthCacheCleaner, CartProvider, useNotification } from '~/hooks';
import { SdkProvider } from '~/sdk'; import { getSdk, SdkProvider } from '~/sdk'; import { fontBody, fontHeadings } from '~/styles/fonts';
import '~/styles/globals.scss';

type AppPropsWithLayout = AppProps & {
  Component: NextPage;
};

function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  const notification = useNotification();

  const [queryClient] = useState(
    () =>
      new QueryClient({
        // ...
      }),
  );

  useEffect(() => {
    // Cypress issue https://github.com/robipop22/dnb-stack/issues/3#issuecomment-1463031001
    document.querySelector('html')?.setAttribute('data-hydrated', 'true');
  }, []);

  return (
    <SdkProvider>
    <SdkProvider sdk={getSdk()}>
      <QueryClientProvider client={queryClient}>
        <DefaultSeo
          {...defaultSeoConfig}
          additionalMetaTags={[{ name: 'viewport', content: 'minimum-scale=1, initial-scale=1, width=device-width' }]}
        />

        <HydrationBoundary state={pageProps.dehydratedState}>
          <CartProvider>
            <AuthCacheCleaner />
            <div className={classNames(fontHeadings.variable, fontBody.variable, 'font-body')}>
              <Component {...pageProps} />
            </div>
          </CartProvider>
        </HydrationBoundary>
        <ReactQueryDevtools />
      </QueryClientProvider>
    </SdkProvider>
  );
}

export default appWithTranslation(MyApp);
  1. Update the Dockerfile responsible for Next.js app. It's in the .vuestorefrontcloud/docker/nextjs/Dockerfile-frontend:
FROM node:18-alpine as base

ARG NPM_USER
ARG NPM_PASS
ARG NPM_EMAIL
ARG NPM_REGISTRY

ARG NEXT_PUBLIC_API_BASE_URLARG NEXT_PUBLIC_MULTISTORE_ENABLEDARG NEXT_PUBLIC_IMAGE_LOADER_FETCH_URLARG NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URLARG NEXT_IMAGE_PROVIDERARG NEXT_DEFAULT_HTML_CACHE_CONTROLENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}ENV NEXT_PUBLIC_MULTISTORE_ENABLED=${NEXT_PUBLIC_MULTISTORE_ENABLED}ENV NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL=${NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL}ENV NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL=${NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL}ENV NEXT_IMAGE_PROVIDER=${NEXT_IMAGE_PROVIDER}ENV NEXT_DEFAULT_HTML_CACHE_CONTROL=${NEXT_DEFAULT_HTML_CACHE_CONTROL}
FROM base AS builder
WORKDIR /var/www

COPY ./package.json ./yarn.lock ./
COPY ./turbo.json .
COPY ./.npmrc .
COPY ./apps/storefront-middleware/ ./apps/storefront-middleware/
COPY ./apps/storefront-unified-nextjs/ ./apps/storefront-unified-nextjs/

RUN apk add --no-cache libc6-compat && \
  npm install -g npm-cli-login && \
  npm-cli-login

RUN yarn install --ignore-scripts && \
  yarn turbo run build --scope="storefront-unified-nextjs"

# First, we copy just package.json to install dependencies first to cache them in Docker layer
# Each Docker command is cached separately. In case when Docker will find that the result of a
# command would be different it will invalidate the cache for the line AND THE FOLLOWING ONES.
# Due to this fact we first copy package.json and install dependencies. If we'd copy sourcecode
# first, then every change in the code would invalidate the cache for dependencies installation
# and the cache mechanism would almost never work in practice.
RUN mkdir -p ./apps/storefront-middleware/ && \
  mkdir -p ./apps/storefront-unified-nextjs/

COPY ./apps/storefront-middleware/package.json ./apps/storefront-middleware/package.json
COPY ./apps/storefront-unified-nextjs/package.json ./apps/storefront-unified-nextjs/package.json

# All the swithes are needed to avoid unnecessary growth of the layer
RUN yarn install --ignore-scripts --no-cache --frozen-lockfile && yarn cache clean

# Then, we copy then application code, and build it
COPY ./apps/storefront-middleware/ ./apps/storefront-middleware/
COPY ./apps/storefront-unified-nextjs/ ./apps/storefront-unified-nextjs/
COPY ./apps/storefront-unified-nextjs/.env.example ./apps/storefront-unified-nextjs/.env
RUN yarn turbo run build --scope="storefront-unified-nextjs"

FROM base AS runner
WORKDIR /var/www

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY .vuestorefrontcloud/docker/nextjs/vue-storefront.sh /usr/local/bin/
RUN chmod a+x /usr/local/bin/vue-storefront.sh

USER nextjs
COPY --from=builder /var/www/apps/storefront-unified-nextjs/next.config.js .
COPY --from=builder /var/www/apps/storefront-unified-nextjs/package.json .COPY --from=builder --chown=nextjs:nodejs /var/www/apps/storefront-unified-nextjs/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /var/www/apps/storefront-unified-nextjs/.next/static ./apps/storefront-unified-nextjs/.next/static
COPY --from=builder --chown=nextjs:nodejs /var/www/apps/storefront-unified-nextjs/public ./apps/storefront-unified-nextjs/public

RUN rm package.json && \
  mv node_modules temp_node_modules && \
  yarn add next-runtime-env@1.7.4 -W --no-lockfile --skip-integrity-check --ignore-scripts --prefer-offline && \
  cp -r temp_node_modules/* ./node_modules/ && \
  rm -rf temp_node_modules && \
  sed -i 's/const nextConfig =/const { configureRuntimeEnv } = require("next-runtime-env\/build\/configure");configureRuntimeEnv();const nextConfig =/' ./apps/storefront-unified-nextjs/server.js

USER nextjs

ENTRYPOINT ["vue-storefront.sh"]
  1. (Optionally) Update the starting script for the frontend app. It's located in .vuestorefrontcloud/docker/nextjs/vue-storefront.sh:
#!/bin/sh
set -e
echo "envs NEXT_PUBLIC_API_BASE_URL"echo $NEXT_PUBLIC_API_BASE_URLecho "envs NEXT_PUBLIC_MULTISTORE_ENABLED"echo $NEXT_PUBLIC_MULTISTORE_ENABLEDecho "envs NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL: $NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL"echo "envs NEXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED: $NEXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED"echo "envs NEXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID: $NEXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID"echo "envs NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL: $NEXT_PUBLIC_IMAGE_LOADER_FETCH_URL"echo "envs NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL: $NEXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL"echo "envs NEXT_DEFAULT_HTML_CACHE_CONTROL: $NEXT_DEFAULT_HTML_CACHE_CONTROL"echo "envs GIT_SHA: $GIT_SHA"node ./server/index.mjs
  1. Update the deployment script. Edit the .github/workflows/continuous-delivery.yml file:
name: Deployment

on:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-frontend:
    name: Build Frontend
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build
        uses: vuestorefront/storefront-deployment/build-frontend@v3.2.0        uses: vuestorefront/storefront-deployment/build-frontend@v4.3.0        with:
          # Change frontend to desired one
          frontend: ${{ vars.FRONTEND_FRAMEWORK || secrets.FRONTEND_FRAMEWORK || 'next' }}
          docker_registry_url: ${{ vars.DOCKER_REGISTRY_URL || secrets.DOCKER_REGISTRY_URL }}
          project_name: ${{ vars.PROJECT_NAME || secrets.PROJECT_NAME }}
          cloud_username: ${{ vars.CLOUD_USERNAME || secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          cloud_region: ${{ vars.CLOUD_REGION || secrets.CLOUD_REGION }}          npm_email: ${{ vars.NPM_EMAIL || secrets.NPM_EMAIL }}
          npm_user: ${{ vars.NPM_USER || secrets.NPM_USER }}
          npm_pass: ${{ secrets.NPM_PASS }}
          api_base_url: ${{ vars.API_BASE_URL || format('https://{0}.{1}.{2}/api', vars.PROJECT_NAME || secrets.PROJECT_NAME, vars.CLOUD_REGION || secrets.CLOUD_REGION, 'gcp.storefrontcloud.io') }}          # Change s-maxage to 15 in order to enable CDN
          default_html_cache_control: ${{ vars.DEFAULT_HTML_CACHE_CONTROL || 'public, max-age=0, s-maxage=0, must-revalidate' }}  build-middleware:
    name: Build Middleware
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build
        uses: vuestorefront/storefront-deployment/build-middleware@v3.2.0        uses: vuestorefront/storefront-deployment/build-middleware@v4.3.0        with:
          docker_registry_url: ${{ vars.DOCKER_REGISTRY_URL || secrets.DOCKER_REGISTRY_URL }}
          project_name: ${{ vars.PROJECT_NAME || secrets.PROJECT_NAME }}
          cloud_username: ${{ vars.CLOUD_USERNAME || secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          npm_email: ${{ vars.NPM_EMAIL || secrets.NPM_EMAIL }}
          npm_user: ${{ vars.NPM_USER || secrets.NPM_USER }}
          npm_pass: ${{ secrets.NPM_PASS }}

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    permissions:
      contents: read
      deployments: write
    needs: [build-frontend, build-middleware]
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Deploy
        uses: vuestorefront/storefront-deployment/deploy@v3        uses: vuestorefront/storefront-deployment/deploy@v4.3.0        with:
          console_api_url: ${{ vars.CONSOLE_API_URL || secrets.CONSOLE_API_URL }}
          docker_registry_url: ${{ vars.DOCKER_REGISTRY_URL || secrets.DOCKER_REGISTRY_URL }}
          project_name: ${{ vars.PROJECT_NAME || secrets.PROJECT_NAME }}
          cloud_username: ${{ vars.CLOUD_USERNAME || secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          cloud_region: ${{ vars.CLOUD_REGION || secrets.CLOUD_REGION }}
  1. Set environment variables in Alokai Console

Go to the Settings -> Environment variables -> Section Storefront Application. Click on the "Add variable", and use the modal fill the name & value of each environment variable. Repeat for all the variables needed to run the app.

How should I found the value of the NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL?

It's the URL of your Storefront, that you can find in the Overview page. Then just add the /api to this address.

Nuxt

  1. Update the @vue-storefront/nuxt package to version 4.1.1
cd apps/storefront-unified-nuxt
yarn add @vue-storefront/nuxt@4.1.1 @vue-storefront/sdk@3.1.0
  1. Update the environment variables

For local development, update the .env.:

# apps/storefront-unified-nuxt/.env
NUXT_PUBLIC_API_BASE_URL="http://localhost:4000"NUXT_PUBLIC_MULTISTORE_ENABLED=falseNUXT_PUBLIC_IMAGE_LOADER_FETCH_URL=https://res.cloudinary.com/dcqchkrzw/image/fetch/NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL=https://res.cloudinary.com/vsf-sap/image/upload/NUXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL="http://localhost:4000"# For CDN cache busting, you can use a hash or a version number. By default, deployed version
# uses the git commit hash. For local development, you can use a random string or skip it.
#NUXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID="example-hash"
NUXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED=falseNUXT_PUBLIC_IMAGE_LOADER_FETCH_URL=https://res.cloudinary.com/dcqchkrzw/image/fetch/NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL=https://res.cloudinary.com/vsf-sap/image/upload/NUXT_DEFAULT_HTML_CACHE_CONTROL="public, max-age=0, s-maxage=15, must-revalidate"IPX_MAX_AGE=31536000

You can manage environment variables for your deployed project in the Console. Go to the Settings -> Environment variables -> Section Storefront Application. Click on the "Add variable", and in the modal fill the name & value of the environment variable. Repeat for all the variables needed to run the app (basically it's all those in the .env file).

  1. Update the Nuxt Config

Remove the vsf key from Nuxt Config. Now all those values will be loaded via Runtime Config by environment variables.

import path from 'node:path';

export default defineNuxtConfig({
  alias: {
    '@sf-modules': path.resolve(__dirname, '.', 'sf-modules'),
    '@sf-modules-middleware': path.resolve(__dirname, '..', 'storefront-middleware', 'sf-modules'),
  },
  vsf: {     middleware: {       apiUrl: import.meta.env.NUXT_PUBLIC_API_BASE_URL,     },     multistore: {       enabled: import.meta.env.NUXT_PUBLIC_MULTISTORE_ENABLED === 'true',     },   },   devtools: { enabled: true },
  devServer: {
    port: 3333,
  },
  // ...
})
  1. Update the Dockerfile responsible for Nuxt app. It's in the .vuestorefrontcloud/docker/nuxtjs/Dockerfile-frontend:
FROM node:18-alpine as base

ARG NPM_USER
ARG NPM_PASS
ARG NPM_EMAIL
ARG NPM_REGISTRY

ARG NUXT_PUBLIC_API_BASE_URLARG NUXT_PUBLIC_MULTISTORE_ENABLEDARG NUXT_PUBLIC_IMAGE_LOADER_FETCH_URLARG NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URLARG NUXT_IMAGE_PROVIDERENV NUXT_PUBLIC_API_BASE_URL=${NUXT_PUBLIC_API_BASE_URL}ENV NUXT_PUBLIC_MULTISTORE_ENABLED=${NUXT_PUBLIC_MULTISTORE_ENABLED}ENV NUXT_PUBLIC_IMAGE_LOADER_FETCH_URL=${NUXT_PUBLIC_IMAGE_LOADER_FETCH_URL}ENV NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL=${NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL}ENV NUXT_IMAGE_PROVIDER=${NUXT_IMAGE_PROVIDER}
FROM base AS builder
WORKDIR /var/www

COPY ./package.json ./yarn.lock ./
COPY ./turbo.json .
COPY ./.npmrc .
COPY ./apps/storefront-middleware/ ./apps/storefront-middleware/
COPY ./apps/storefront-unified-nuxt/ ./apps/storefront-unified-nuxt/

RUN apk add --no-cache libc6-compat && \
  npm install -g npm-cli-login && \
  npm-cli-login

RUN yarn install --ignore-scripts && \  yarn turbo run build --scope="storefront-unified-nuxt"
# First, we copy just package.json to install dependencies first to cache them in Docker layer
# Each Docker command is cached separately. In case when Docker will find that the result of a
# command would be different it will invalidate the cache for the line AND THE FOLLOWING ONES.
# Due to this fact we first copy package.json and install dependencies. If we'd copy sourcecode
# first, then every change in the code would invalidate the cache for dependencies installation
# and the cache mechanism would almost never work in practice.
RUN mkdir -p ./apps/storefront-middleware/ && \
  mkdir -p ./apps/storefront-unified-nuxt/

COPY ./apps/storefront-middleware/package.json ./apps/storefront-middleware/package.json
COPY ./apps/storefront-unified-nuxt/package.json ./apps/storefront-unified-nuxt/package.json

# All the swithes are needed to avoid unnecessary growth of the layer
RUN yarn install --ignore-scripts --no-cache --frozen-lockfile && yarn cache clean

# Then, we copy then application code, and build it
COPY ./apps/storefront-middleware/ ./apps/storefront-middleware/
COPY ./apps/storefront-unified-nuxt/ ./apps/storefront-unified-nuxt/

ARG NUXT_IMAGE_PROVIDER
ENV NUXT_IMAGE_PROVIDER=${NUXT_IMAGE_PROVIDER}
RUN yarn turbo run build --scope="storefront-unified-nuxt"

FROM base AS runner
WORKDIR /var/www

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nuxtjs
COPY .vuestorefrontcloud/docker/nuxtjs/vue-storefront.sh /usr/local/bin/
RUN chmod a+x /usr/local/bin/vue-storefront.sh

USER nuxtjs

COPY --from=builder --chown=nuxtjs:nodejs /var/www/apps/storefront-unified-nuxt/.output ./

ENTRYPOINT ["vue-storefront.sh"]

Why the NUXT_IMAGE_PROVIDER is still passed as argument if this we talking about no hardcoding ENVs?

Very good question. Reason for that is how the Nuxt Image module is built. It uses NUXT_IMAGE_PROVIDER environment variable only in build time. So to switch image provider, you still need to modify the value of the image_provider in build-frontend action in your deployment script.

  1. (Optionally) Update the starting script for the frontend app. It's in the .vuestorefrontcloud/docker/nuxtjs/vue-storefront.sh:
#!/bin/sh
set -e
echo "envs NUXT_PUBLIC_API_BASE_URL"echo $NUXT_PUBLIC_API_BASE_URLecho "envs NUXT_PUBLIC_MULTISTORE_ENABLED"echo $NUXT_PUBLIC_MULTISTORE_ENABLEDecho "envs NUXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL: $NUXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL"echo "envs NUXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED: $NUXT_PUBLIC_ALOKAI_MULTISTORE_ENABLED"echo "envs NUXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID: $NUXT_PUBLIC_ALOKAI_MIDDLEWARE_CDN_CACHE_BUSTING_ID"echo "envs NUXT_PUBLIC_IMAGE_LOADER_FETCH_URL: $NUXT_PUBLIC_IMAGE_LOADER_FETCH_URL"echo "envs NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL: $NUXT_PUBLIC_IMAGE_LOADER_UPLOAD_URL"echo "envs NUXT_DEFAULT_HTML_CACHE_CONTROL: $NUXT_DEFAULT_HTML_CACHE_CONTROL"echo "envs IPX_MAX_AGE: $IPX_MAX_AGE"echo "envs GIT_SHA: $GIT_SHA"node ./server/index.mjs
  1. Update the deployment script. Edit the .github/workflows/continuous-delivery.yml file:
name: Deployment

on:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-frontend:
    name: Build Frontend
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build
        uses: vuestorefront/storefront-deployment/build-frontend@v3.2.0        uses: vuestorefront/storefront-deployment/build-frontend@v4.3.0        with:
          # Change frontend to desired one
          frontend: ${{ vars.FRONTEND_FRAMEWORK || secrets.FRONTEND_FRAMEWORK || 'next' }}
          docker_registry_url: ${{ vars.DOCKER_REGISTRY_URL || secrets.DOCKER_REGISTRY_URL }}
          project_name: ${{ vars.PROJECT_NAME || secrets.PROJECT_NAME }}
          cloud_username: ${{ vars.CLOUD_USERNAME || secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          cloud_region: ${{ vars.CLOUD_REGION || secrets.CLOUD_REGION }}          npm_email: ${{ vars.NPM_EMAIL || secrets.NPM_EMAIL }}
          npm_user: ${{ vars.NPM_USER || secrets.NPM_USER }}
          npm_pass: ${{ secrets.NPM_PASS }}
          api_base_url: ${{ vars.API_BASE_URL || format('https://{0}.{1}.{2}/api', vars.PROJECT_NAME || secrets.PROJECT_NAME, vars.CLOUD_REGION || secrets.CLOUD_REGION, 'gcp.storefrontcloud.io') }}          # Change s-maxage to 15 in order to enable CDN
          default_html_cache_control: ${{ vars.DEFAULT_HTML_CACHE_CONTROL || 'public, max-age=0, s-maxage=0, must-revalidate' }}  build-middleware:
    name: Build Middleware
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build
        uses: vuestorefront/storefront-deployment/build-middleware@v3.2.0        uses: vuestorefront/storefront-deployment/build-middleware@v4.3.0        with:
          docker_registry_url: ${{ vars.DOCKER_REGISTRY_URL || secrets.DOCKER_REGISTRY_URL }}
          project_name: ${{ vars.PROJECT_NAME || secrets.PROJECT_NAME }}
          cloud_username: ${{ vars.CLOUD_USERNAME || secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          npm_email: ${{ vars.NPM_EMAIL || secrets.NPM_EMAIL }}
          npm_user: ${{ vars.NPM_USER || secrets.NPM_USER }}
          npm_pass: ${{ secrets.NPM_PASS }}

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    permissions:
      contents: read
      deployments: write
    needs: [build-frontend, build-middleware]
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Deploy
        uses: vuestorefront/storefront-deployment/deploy@v3        uses: vuestorefront/storefront-deployment/deploy@v4.3.0        with:
          console_api_url: ${{ vars.CONSOLE_API_URL || secrets.CONSOLE_API_URL }}
          docker_registry_url: ${{ vars.DOCKER_REGISTRY_URL || secrets.DOCKER_REGISTRY_URL }}
          project_name: ${{ vars.PROJECT_NAME || secrets.PROJECT_NAME }}
          cloud_username: ${{ vars.CLOUD_USERNAME || secrets.CLOUD_USERNAME }}
          cloud_password: ${{ secrets.CLOUD_PASSWORD }}
          cloud_region: ${{ vars.CLOUD_REGION || secrets.CLOUD_REGION }}
  1. Set environment variables in Alokai Console

Go to the Settings -> Environment variables -> Section Storefront Application. Click on the "Add variable", and use the modal fill the name & value of each environment variable. Repeat for all the variables needed to run the app.

How should I found the value of the NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL?

It's the URL of your Storefront, that you can find in the Overview page. Then just add the /api to this address.