Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build fails on Docker with app initializer that does http request #28999

Open
1 task
k-schneider opened this issue Nov 29, 2024 · 1 comment
Open
1 task

Build fails on Docker with app initializer that does http request #28999

k-schneider opened this issue Nov 29, 2024 · 1 comment
Assignees

Comments

@k-schneider
Copy link

Command

build

Is this a regression?

  • Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

No response

Description

I have an Angular v19 app that is failing within a docker build due to prerendering (even though I don't have any prerendered routes). The build succeeds when ran directly on my Windows workstation.

Minimal Reproduction

I have a really simple app configuration service/provider that exposes environment variables to the client:

export const provideAppConfig = () =>
  provideAppInitializer(() => appConfigInitializer(inject(AppConfigService)));

function appConfigInitializer(appConfigService: AppConfigService) {
  return appConfigService.load();
}

@Injectable({
  providedIn: "root",
})
export class AppConfigService {
  private http = inject(HttpClient);
  value = signal<AppConfig>({});

  public async load(): Promise<void> {
    const config = await firstValueFrom(
      this.http.get<AppConfig>("/app-config"),
    );
    this.value.set(config);
  }

  public get(key: string): any {
    return this.value()[key];
  }
}

interface AppConfig {
  [key: string]: any;
}

And an express route that serves back json:

const route = Router();

/**
 * Returns any public environment variables that start with PUBLIC_NG_ for use in the Angular app
 */
route.get("/app-config", (req, res) => {
  const config: Record<string, any> = {};
  Object.keys(process.env).forEach((key) => {
    if (key.startsWith("PUBLIC_NG_")) {
      config[key] = process.env[key];
    }
  });
  res.json(config);
});

export default route;

The Dockerfile for reference is:

# Use Node.js for building the Angular SSR app
FROM node:22 AS builder

# Set the working directory
WORKDIR /app

# Copy package.json and install dependencies
COPY package*.json ./
RUN npm ci

# Copy all files and build the Angular SSR app
COPY . .
RUN npm run build

# Use a minimal Node.js image to run the SSR server
FROM node:22-alpine AS server

# Set the working directory
WORKDIR /app

# Copy the built app from the builder stage
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package.json /app

# Install serve to serve the SSR app
RUN npm install -g serve

# Expose the SSR server port
EXPOSE 4000

# Command to run the SSR server
CMD ["npm", "run", "serve:ssr"]

Exception or Error

0.233 > ng build
0.233
0.541 ❯ Building...
0.944 (node:20) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
0.944 (Use `node --trace-warnings ...` to show where the warning was created)
4.862 ERROR vt {
4.862   headers: e {
4.862     headers: Map(0) {},
4.862     normalizedNames: Map(0) {},
4.862     lazyInit: undefined,
4.862     lazyUpdate: null
4.862   },
4.862   status: 0,
4.862   statusText: 'Unknown Error',
4.862   url: 'http://localhost:37773/app-config',
4.862   ok: false,
4.862   type: undefined,
4.862   name: 'HttpErrorResponse',
4.862   message: 'Http failure response for http://localhost:37773/app-config: 0 undefined',
4.862   error: TypeError: fetch failed
4.862       at node:internal/deps/undici/undici:13178:13
4.862       at a.invoke (file:///app/.angular/prerender-root/6d2a3b7b-b719-4cf2-96bb-7b6c75bc01a4/polyfills.server.mjs:3:6715)
4.862       at l.run (file:///app/.angular/prerender-root/6d2a3b7b-b719-4cf2-96bb-7b6c75bc01a4/polyfills.server.mjs:3:1985)
4.862       at file:///app/.angular/prerender-root/6d2a3b7b-b719-4cf2-96bb-7b6c75bc01a4/polyfills.server.mjs:4:576
4.862       at a.invokeTask (file:///app/.angular/prerender-root/6d2a3b7b-b719-4cf2-96bb-7b6c75bc01a4/polyfills.server.mjs:3:7341)
4.862       at l.runTask (file:///app/.angular/prerender-root/6d2a3b7b-b719-4cf2-96bb-7b6c75bc01a4/polyfills.server.mjs:3:2628)
4.862       at R (file:///app/.angular/prerender-root/6d2a3b7b-b719-4cf2-96bb-7b6c75bc01a4/polyfills.server.mjs:3:9382)
4.862       at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
4.862     [cause]: Error: connect ECONNREFUSED 127.0.0.1:37773
4.862         at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1607:16) {
4.862       errno: -111,
4.862       code: 'ECONNREFUSED',
4.862       syscall: 'connect',
4.862       address: '127.0.0.1',
4.862       port: 37773
4.862     }
4.862   }
4.862 }

Your Environment

Angular CLI: 19.0.2
Node: 20.18.0
Package Manager: npm 10.8.2
OS: win32 x64

Angular: 19.0.1
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1900.2
@angular-devkit/build-angular   19.0.2
@angular-devkit/core            19.0.2
@angular-devkit/schematics      19.0.2
@angular/cli                    19.0.2
@angular/ssr                    19.0.2
@schematics/angular             19.0.2
rxjs                            7.8.1
typescript                      5.6.3
zone.js                         0.15.0

Anything else relevant?

If I put a try/catch around the http request and suppress the error the build succeeds.

@alan-agius4 alan-agius4 self-assigned this Dec 2, 2024
@alan-agius4 alan-agius4 added type: bug/fix freq1: low Only reported by a handful of users who observe it rarely severity3: broken angular/build:application needs: more info Reporter must clarify the issue area: @angular/build and removed type: bug/fix freq1: low Only reported by a handful of users who observe it rarely severity3: broken angular/build:application labels Dec 2, 2024
@alan-agius4
Copy link
Collaborator

alan-agius4 commented Dec 2, 2024

Can you try to replace node_modules/@angular/build/src/utils/server-rendering/launch-server.js to the below and check if the problem persists?

"use strict";
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_URL = void 0;
exports.launchServer = launchServer;
const node_assert_1 = __importDefault(require("node:assert"));
const node_http_1 = require("node:http");
const load_esm_1 = require("../load-esm");
const load_esm_from_memory_1 = require("./load-esm-from-memory");
const utils_1 = require("./utils");
exports.DEFAULT_URL = new URL('http://ng-localhost/');
/**
 * Launches a server that handles local requests.
 *
 * @returns A promise that resolves to the URL of the running server.
 */
async function launchServer() {
    const { reqHandler } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./server.mjs');
    const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
    if (!(0, utils_1.isSsrNodeRequestHandler)(reqHandler) && !(0, utils_1.isSsrRequestHandler)(reqHandler)) {
        return exports.DEFAULT_URL;
    }
    const server = (0, node_http_1.createServer)((req, res) => {
        (async () => {
            // handle request
            if ((0, utils_1.isSsrNodeRequestHandler)(reqHandler)) {
                await reqHandler(req, res, (e) => {
                    throw e;
                });
            }
            else {
                const webRes = await reqHandler(createWebRequestFromNodeRequest(req));
                if (webRes) {
                    await writeResponseToNodeResponse(webRes, res);
                }
                else {
                    res.statusCode = 501;
                    res.end('Not Implemented.');
                }
            }
        })().catch((e) => {
            res.statusCode = 500;
            res.end('Internal Server Error.');
            // eslint-disable-next-line no-console
            console.error(e);
        });
    });
    server.unref();
    await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
    const serverAddress = server.address();
    (0, node_assert_1.default)(serverAddress, 'Server address should be defined.');
    (0, node_assert_1.default)(typeof serverAddress !== 'string', 'Server address should not be a string.');
    return new URL(`http://${serverAddress.address}:${serverAddress.port}/`);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants