Unified Angular Deployment: One Build, Environment-Specific Configs via Docker and Nginx

<h2 id="config-strategy">Designing a Configuration Strategy</h2> <p>Standard Angular builds embed environment variables directly into the code during compilation. This forces separate builds for each environment—Dev, Staging, and Production—which is inefficient and error-prone. A smarter approach is to compile a single Docker image and inject the correct configuration at runtime. This article details a robust method using environment-specific JSON configuration files and a Docker entrypoint script.</p><figure style="margin:20px 0"><img src="https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vwcoj9exa4a6au5y7r4.png" alt="Unified Angular Deployment: One Build, Environment-Specific Configs via Docker and Nginx" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px">Source: dev.to</figcaption></figure> <h3 id="creating-config-files">Creating Environment-Specific Configuration Files</h3> <p>In your Angular project's <code>src/config</code> folder, create JSON files for each environment, for example <code>app-config.dev.json</code> and <code>app-config.prod.json</code>. Additionally, place a default <code>app-config.json</code> directly in the <code>src/</code> folder for local development. This approach ensures that the application can be tested locally with its typical configuration while keeping environment-specific values separate.</p> <h3 id="including-config-files">Including Config Files in Build Assets</h3> <p>To ensure these files are shipped with the built application, update the <code>angular.json</code> configuration file under the project's <code>architect.build.options.assets</code> array. Add the following entries:</p> <pre><code>&quot;assets&quot;: [ &quot;src/favicon.ico&quot;, &quot;src/assets&quot;, &quot;src/app-config.json&quot;, &quot;src/config&quot; ]</code></pre> <p>This will copy the config folder and the default config file into the build output, making them accessible at runtime.</p> <h2 id="angular-implementation">Implementing Dynamic Configuration Loading in Angular</h2> <p>The Angular application must load the correct configuration before the app initializes, using the <code>HttpClient</code> to fetch the JSON file that will be set by the Docker entrypoint.</p> <h3 id="config-service">Building a Config Service</h3> <p>Create an injectable service that fetches the configuration file from the server root. The service uses Angular's <code>HttpClient</code> and returns a promise that resolves when the config is loaded.</p> <pre><code>@Injectable({ providedIn: &#39;root&#39; }) export class ConfigService { private config: any; constructor(private http: HttpClient) {} loadConfig() { return firstValueFrom(this.http.get(&#39;./app-config.json&#39;)) .then(data =&gt; this.config = data); } get settings() { return this.config; } }</code></pre> <p>Note: The path <code>./app-config.json</code> refers to the root of the web server, which in our Docker image will point to the Nginx served folder.</p> <h3 id="app-initializer">Initializing the App with a Bootstrap Guard</h3> <p>To prevent the application from starting before the configuration is available, use Angular's <code>APP_INITIALIZER</code> provider. This can be added in <code>app.config.ts</code> (for standalone applications) or in the <code>AppModule</code> for module‐based setups.</p> <pre><code>export function initApp(configService: ConfigService) { return () =&gt; configService.loadConfig(); } // In providers array: { provide: APP_INITIALIZER, useFactory: initApp, deps: [ConfigService], multi: true }</code></pre> <p>With this setup, the Angular app waits for <code>ConfigService.loadConfig()</code> to complete before rendering any components, ensuring that all environment-specific values are available.</p> <h2 id="docker-containerization">Containerizing with Docker: The Entrypoint Approach</h2> <p>Now we package the built Angular application into a Docker image using a multi‐stage build. The key is to use a custom entrypoint script that swaps the configuration files based on the <code>ENVIRONMENT</code> environment variable.</p> <h3 id="entrypoint-script">Crafting the Entrypoint Script</h3> <p>Create a file named <code>entrypoint.sh</code> in the project root. This shell script runs when the container starts, checks the <code>ENVIRONMENT</code> variable, and overwrites the default <code>app-config.json</code> with the appropriate environment-specific file.</p><figure style="margin:20px 0"><img src="https://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F347695%2F0883cd79-cada-4653-b32b-7db298946057.jpeg" alt="Unified Angular Deployment: One Build, Environment-Specific Configs via Docker and Nginx" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px">Source: dev.to</figcaption></figure> <pre><code>#!/bin/bash if [[ $ENVIRONMENT == &quot;Prod&quot; ]]; then cp /usr/share/nginx/html/config/app-config.prod.json /usr/share/nginx/html/app-config.json else cp /usr/share/nginx/html/config/app-config.dev.json /usr/share/nginx/html/app-config.json fi nginx -g &#39;daemon off;&#39;</code></pre> <p>Note: The paths assume that the Nginx root is <code>/usr/share/nginx/html</code>, which is the default in the official Nginx image. The script copies the correct config file over the default <code>app-config.json</code> before starting Nginx.</p> <h3 id="dockerfile">Building the Multi-Stage Dockerfile</h3> <p>Use two stages: one for building the Angular application with Node.js, and a second to serve it with Nginx. The final image is small and contains only the built files and the entrypoint script.</p> <pre><code># Build Stage FROM node:lts AS build WORKDIR /app COPY . . RUN npm install &amp;&amp; npm run build # Run Stage FROM nginx:latest COPY --from=build /app/dist/your-app-name/browser /usr/share/nginx/html COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT [&quot;/bin/bash&quot;, &quot;/entrypoint.sh&quot;]</code></pre> <p>Replace <code>your-app-name</code> with the actual output directory name from your Angular build (usually found in <code>dist/</code>). The entrypoint is set as the container startup command.</p> <h2 id="running-container">Running the Container Across Environments</h2> <p>With the Docker image built, you can now deploy the same image to any environment simply by passing the appropriate <code>ENVIRONMENT</code> variable.</p> <h3 id="building-image">Building the Docker Image</h3> <p>Run the following command in the project root to build the image once:</p> <pre><code>docker build -t my-angular-app .</code></pre> <h3 id="running-with-env">Running with Environment Variables</h3> <p>Start the container for different environments by setting the <code>ENVIRONMENT</code> variable:</p> <ul> <li><strong>Production</strong>: <code>docker run -e ENVIRONMENT=Prod -p 8080:80 my-angular-app</code></li> <li><strong>Development</strong>: <code>docker run -e ENVIRONMENT=Dev -p 8080:80 my-angular-app</code></li> </ul> <p>The same image works everywhere—no need to rebuild for each environment. The entrypoint script automatically picks the right configuration based on the environment variable.</p> <h2 id="conclusion">Conclusion</h2> <p>By decoupling configuration from the build process, you can streamline Angular deployments, reduce build times, and eliminate environment-specific errors. The combination of runtime configuration loading in Angular and a Docker entrypoint script allows you to build once and deploy anywhere. This approach is production-ready and can easily be extended with additional environment files or more complex logic.</p>
Tags: