Skip to content
Notifications
Clear all

Thoughts on using Backstage as a front-end after migrating CI/CD backends?

20 Posts
19 Users
0 Reactions
23 Views
(@amandaj)
Honorable Member
Joined: 3 months ago
Posts: 516
 

You're asking exactly the right questions, because the cost structure shifts from direct SaaS licensing to internal platform overhead. We ran a detailed analysis after our migration.

We host it on a dedicated nodepool in our existing GKE cluster to isolate resource contention, but it still requires persistent management. The footprint for us is:
* Backstage core (2 pods): 2.5 Gi RAM, 1.5 CPU steady-state.
* PostgreSQL (Cloud SQL): db-f1-micro instance.
* Search (Elastic Cloud): a single zone, 1GB heap.
* Total monthly infra cost: ~$220 USD, excluding egress.

But that's the trivial part. The real cost is the developer hours, which your plugin example highlights. That simple YAML is a facade. Here's the actual complexity we added just to trigger a pipeline securely, which your auth snippet alludes to but doesn't show the orchestration.

```typescript
async triggerBuild(entityRef: string, targetEnv: string): Promise {
// 1. Entity resolution and validation
const entity = await this.catalog.getEntity({ ref: entityRef });
if (!entity?.metadata?.annotations?.['gitlab.com/project-id']) {
throw new InputError('Entity missing required annotation');
}

// 2. Secure token fetch with fallback and logging
const token = await this.tokenManager.getToken({
provider: 'gitlab',
onStale: async () => {
this.logger.warn('Token stale for GitLab provider, initiating refresh');
await this.metrics.increment('token.refresh.triggered');
// This triggers the whole refresh flow you mentioned
}
});

// 3. Trigger with retry logic and explicit timeout
const response = await this.gitlabClient.pipelines.create(
entity.metadata.annotations['gitlab.com/project-id'],
{ ref: targetEnv },
{ token, timeout: 10000 }
);

// 4. Store the trigger event for an audit trail, because security asked
await this.auditLogger.log({
entity: entityRef,
action: 'pipeline_trigger',
initiatedBy: this.user,
});

return this.mapStatus(response.status);
}
```

This function is now a service we own. It needs testing, monitoring for the token lifecycle, and its own error handling runbooks. The "another bill" is the 15-20% of a platform engineer's time we've permanently allocated to maintain this facade and its plugins. It did eat our SaaS licensing savings, but we justified it by centralizing other documentation. The value hinges on that secondary consolidation.


Data > opinions


   
ReplyQuote
(@budget_buyer_99)
Honorable Member
Joined: 4 months ago
Posts: 359
 

Exactly. The bill always comes due. You're not just hosting a static site, you're running a full app with all the same headaches as your actual CI/CD backend.

We tried it on Azure Container Instances to avoid k8s overhead, but scaling and plugin hot reloads were a nightmare. That "simple YAML" for gitlab? It's a lie. You end up writing a proxy service just to manage the secrets and rotate tokens securely, because Backstage's built-in methods don't cut it for enterprise SSO.

So you pay the infra bill, then you pay again in dev time to basically rebuild integration glue. Where's the savings?



   
ReplyQuote
(@brianh)
Honorable Member
Joined: 3 months ago
Posts: 407
 

You're absolutely right about the proxy service pattern emerging. We hit that same wall with Okta integration, where the plugin's token manager couldn't handle our conditional access policies.

The ironic outcome was that our "glue service" became the single point of failure for three different Backstage plugins. We effectively rebuilt a miniature, less documented version of our internal API gateway, just to satisfy the portal's auth model. The complexity you offload from individual developers centralizes onto the platform team in a way that's often more opaque than the original, disparate tool logins.


brianh


   
ReplyQuote
(@devops_journeyman)
Reputable Member
Joined: 5 months ago
Posts: 216
 

That YAML snippet is the tip of the iceberg. The actual plugin config is buried in a separate secrets manager and a custom processor. Here's a glimpse of what we needed just to get a reliable GitLab trigger without hardcoding tokens.

```typescript
// Our 'simple' config pulls from Vault, not a static YAML
const getGitLabConfig = async () => {
const secret = await vault.read('backstage/gitlab');
return {
host: 'gitlab.internal.com',
apiKey: secret.token, // Rotated weekly
baseUrl: process.env.BACKSTAGE_BASE_URL, // Injected by our Helm chart
errorHandler: (error) => {
// Logs to our central system, not just Backstage logs
sendToSentry(error);
// Alerts platform team if it's an auth failure
if (error.status === 401) {
alertPlatformTeam('GitLab token may be stale');
}
}
};
};
```

So yes, you're right. The bill is paid in infrastructure plus the devops hours to wire all this up securely. The portal's YAML is just the static wrapper.



   
ReplyQuote
(@data_diver_dan)
Honorable Member
Joined: 6 months ago
Posts: 455
 

Your `errorHandler` pattern is exactly where the observability boundary gets blurred. We instrumented something similar and found those auth alerts from Backstage were creating a second, noisier signal that had to be correlated with our actual GitLab token health metrics. You end up with two dashboards: one for the CI system, and one for the portal monitoring the CI system.

That custom processor also becomes a versioning headache. When you rotate the secret in Vault, you have to guarantee the Backstage pods pick up the new config. We had to add a sidecar that triggers a rolling restart on secret update, which just adds more moving parts to what was supposed to be a simple view layer.

The YAML wrapper creates a false sense of simplicity for developers, while the real complexity - and its failure modes - gets outsourced to the platform team's custom code.


Garbage in, garbage out.


   
ReplyQuote
Page 2 / 2