DEV Community

Basit Jamil
Basit Jamil

Posted on

Build an Angular app once, Deploy Anywhere with Docker & Nginx

Standard Angular builds bake environment variables into the code at build time. This means if you have Dev, Staging, and Prod environments, you have to build the app three times.

That's a waste of time. Instead, we should build a single Docker image and swap configurations at runtime. Here is how to do it.


1. The Strategy

We will store environment-specific configurations in JSON files and use an Entrypoint script in Docker to swap them based on an environment variable.

Step 1: Create Config Files

In your src/config folder, create:

  • app-config.dev.json
  • app-config.prod.json

In your src/ folder, create a default app-config.json for local development.

Step 2: Update angular.json

Ensure these files are included in your build assets:

"assets": [
  "src/favicon.ico",
  "src/assets",
  "src/app-config.json",
  "src/config" 
]
Enter fullscreen mode Exit fullscreen mode

2. The Angular Logic

We need to load the JSON file before the app starts.

The Config Service

Create a service to fetch the JSON file using HttpClient.

@Injectable({ providedIn: 'root' })
export class ConfigService {
  private config: any;

  constructor(private http: HttpClient) {}

  loadConfig() {
    return firstValueFrom(this.http.get('./app-config.json'))
      .then(data => this.config = data);
  }

  get settings() {
    return this.config;
  }
}
Enter fullscreen mode Exit fullscreen mode

The App Initializer

Use APP_INITIALIZER in your app.config.ts (Standalone) or AppModule to block the bootstrap process until the config is loaded.

export function initApp(configService: ConfigService) {
  return () => configService.loadConfig();
}

// In providers:
{
  provide: APP_INITIALIZER,
  useFactory: initApp,
  deps: [ConfigService],
  multi: true
}
Enter fullscreen mode Exit fullscreen mode

3. The Docker Magic

Now, we create a Docker image that can adapt to its environment.

entrypoint.sh

This script runs when the container starts. It checks the $ENVIRONMENT variable and overwrites the main config file.

#!/bin/bash
if [[ $ENVIRONMENT == "Prod" ]]; 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 'daemon off;'
Enter fullscreen mode Exit fullscreen mode

Dockerfile

Use a multi-stage build to compile the app and serve it with Nginx.

# Build Stage
FROM node:lts AS build
WORKDIR /app
COPY . .
RUN npm install && 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 ["/bin/bash", "/entrypoint.sh"]
Enter fullscreen mode Exit fullscreen mode

4. Running the Container

Now you can build your image once:

docker build -t my-angular-app .
Enter fullscreen mode Exit fullscreen mode

And run it anywhere by just changing the environment variable:

# Run for Production
docker run -e ENVIRONMENT=Prod -p 8080:80 my-angular-app

# Run for Dev
docker run -e ENVIRONMENT=Dev -p 8080:80 my-angular-app
Enter fullscreen mode Exit fullscreen mode

Summary

  • One Build: Saves CI/CD time.
  • Nginx: High-performance serving.
  • Environment Agnostic: Move the same artifact from Dev to Prod with zero code changes.

Happy coding! πŸš€

Top comments (0)