Serve preview URLs over Traefik+TLS, stabilize them, and speed up builds.
Ingress / preview URLs: - Default the app Ingress class and ACME HTTP-01 solver to Traefik (k3s default) via a new INGRESS_CLASS env, instead of hardcoding nginx — fixes 404s on clusters without ingress-nginx. - Only put public, real-TLD hosts (custom domain + preview) in the TLS block; the internal *.apps.cloudhost.local host no longer poisons the Let's Encrypt order, so certs actually issue. - Make the per-app preview number stable across redeploys so URLs stop breaking, and let PREVIEW_BASE_DOMAIN configure the base domain. Registry pulls: - Point the k3s registries.yaml mirror endpoint at the registry NodePort on loopback so node containerd never depends on cluster DNS (image pulls survive node restarts). Builds: - Pin the Kaniko image, use IfNotPresent pull policy, drop the dead build queue/processor, and retry transient Kubernetes API errors while polling build jobs. Logs & apps list: - fluent-bit reads log files from head so startup output reaches Elasticsearch. - Order joined deployments newest-first so the apps list shows the latest deployment status. Allocation: - Reserve in-flight (pending/building) capacity and stop globally degrading the cluster on a single allocation failure, so concurrent deploys don't starve or wrongly report "no healthy cluster". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -30,13 +30,29 @@ export class DeploymentsService {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Stable "number" part for preview host, derived from deployment id.
|
||||
* We keep it numeric to match the "<number>" requirement.
|
||||
* Random 7-digit suffix for the preview host: <userId>-<7-digit>.<baseDomain>.
|
||||
* Generated once per application (see resolvePreviewNumber) and persisted.
|
||||
*/
|
||||
private computePreviewNumberFromDeploymentId(deploymentId: string): string {
|
||||
const hashHex = crypto.createHash('sha256').update(deploymentId).digest('hex');
|
||||
const num = parseInt(hashHex.slice(0, 8), 16) % 1_000_000; // 0..999999
|
||||
return String(num).padStart(6, '0');
|
||||
private generatePreviewNumber(): string {
|
||||
return String(crypto.randomInt(1_000_000, 10_000_000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a STABLE preview number for an application: reuse the one already
|
||||
* assigned to a previous deployment so the public preview URL never changes
|
||||
* across redeploys (otherwise old links 404). Only generates a new number the
|
||||
* first time the app is deployed. Apps with a custom domain get no preview host.
|
||||
*/
|
||||
private async resolvePreviewNumber(applicationId: string): Promise<string> {
|
||||
const existing = await this.deploymentsRepository
|
||||
.createQueryBuilder('d')
|
||||
.select('d.previewSubdomain', 'previewSubdomain')
|
||||
.where('d.applicationId = :applicationId', { applicationId })
|
||||
.andWhere('d.previewSubdomain IS NOT NULL')
|
||||
.orderBy('d.createdAt', 'DESC')
|
||||
.limit(1)
|
||||
.getRawOne<{ previewSubdomain: string }>();
|
||||
return existing?.previewSubdomain || this.generatePreviewNumber();
|
||||
}
|
||||
|
||||
async triggerDeployment(applicationId: string, userId: string): Promise<Deployment> {
|
||||
@@ -56,7 +72,7 @@ export class DeploymentsService {
|
||||
// Fill deterministic preview number after we have the deployment id.
|
||||
let previewSubdomain: string | null = null;
|
||||
if (!app.customDomain) {
|
||||
previewSubdomain = this.computePreviewNumberFromDeploymentId(saved.id);
|
||||
previewSubdomain = await this.resolvePreviewNumber(app.id);
|
||||
await this.deploymentsRepository.update(saved.id, { previewSubdomain });
|
||||
saved.previewSubdomain = previewSubdomain;
|
||||
}
|
||||
@@ -657,7 +673,7 @@ export class DeploymentsService {
|
||||
|
||||
let previewSubdomain: string | null = null;
|
||||
if (!app.customDomain) {
|
||||
previewSubdomain = this.computePreviewNumberFromDeploymentId(saved.id);
|
||||
previewSubdomain = await this.resolvePreviewNumber(app.id);
|
||||
await this.deploymentsRepository.update(saved.id, { previewSubdomain });
|
||||
saved.previewSubdomain = previewSubdomain;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user