Skip to main content

Managing Portal Connect

This guide covers everything you need to configure, operate, monitor, and troubleshoot Portal Connect in production.

Configuring Plugins

Once you have Portal Connect Agent deployed and running, you can configure your Backstage plugins to access internal services through the Agent. The key principle is simple: configure plugins as if they could access the services directly within the same network.

How It Works

When you configure a Backstage plugin to access an internal service:

  1. Use the internal hostname/URL directly in your plugin configuration (e.g., https://github.internal.company.com, https://artifactory.corp.net)
  2. The Agent registers routes for these internal services with Portal
  3. When Portal makes a request to that URL, Portal Connect automatically tunnels the request through the Agent
  4. The Agent executes the request from within your network and returns the response

No special configuration needed. Portal Connect handles the routing transparently once the Agent has registered the route.

Configuration Approach

Internal Service URLs

Configure your plugins with internal URLs exactly as you would if Portal were running inside your network:

Example - GitHub Enterprise:

github:
- host: github.internal.company.com
apiBaseUrl: https://github.internal.company.com/api/v3
token: ${GITHUB_TOKEN} # or dummy value

Example - Internal Artifactory:

catalog:
locations:
- type: url
target: https://artifactory.corp.net/api/storage/backstage/catalog-info.yaml

Authentication and Credentials

For plugin configuration that requires authentication headers or API tokens:

Option 1: Use dummy values in plugin configuration

Since the Portal Connect Agent injects the real authentication headers (configured in the Agent's routes), you can use placeholder values in your plugin configuration:

integrations:
github:
- host: github.internal.company.com
token: dummy-token-not-used # Real token configured in Agent

Option 2: Reference environment variables

You can reference environment variables in your configuration, but the actual authentication will happen at the Agent level:

integrations:
github:
- host: github.internal.company.com
token: ${GITHUB_TOKEN} # Can be dummy value

The Agent configuration would include the real authentication:

# Agent app-config.yaml
agent:
routes:
'https://github.internal.company.com/*':
headers:
authorization: 'Bearer ${REAL_GITHUB_TOKEN}'

Multi-Step Configuration Example

Step 1: Configure the Agent with the route and real credentials:

# Portal Connect Agent configuration
agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com

routes:
'https://github.internal.company.com/*':
headers:
authorization: 'Bearer ${GITHUB_ENTERPRISE_TOKEN}'

'https://artifactory.corp.net/*':
headers:
x-api-key: '${ARTIFACTORY_API_KEY}'

mtls:
clientCertPath: /certs/portal-connect-client-<timestamp>.crt
clientKeyPath: /certs/portal-connect-client-<timestamp>.key

Step 2: Configure your plugin to use the internal URL:

integrations:
github:
- host: github.internal.company.com
apiBaseUrl: https://github.internal.company.com/api/v3
token: placeholder # Real token injected by Agent

Step 3: Deploy or restart the Agent to register the routes.

Step 4: Your plugins now work seamlessly—Portal Connect handles the routing and authentication.

Benefits of This Approach

  • Simple plugin configuration - No special proxy settings or complex routing rules in Backstage
  • Centralized credential management - All internal service credentials stay within your network at the Agent level
  • Security - Credentials never leave your network perimeter
  • Transparency - Plugins work as if Portal were running inside your network
  • Maintainability - Plugin configurations remain clean and straightforward

Adding and Managing Routes

This section covers how to add, modify, and remove routes in Portal Connect Agent.

Understanding Routes

Routes define which external URLs the Agent can access on behalf of your applications.

Route Components

A route consists of:

  • Protocol: http:// or https://
  • Hostname: Domain name or IP address
  • Path: Optional path component (exact or wildcard)
  • Headers: Optional per-route default headers (advanced)

Route Matching

Exact match:

routes:
- https://api.github.com/users
  • Matches only: https://api.github.com/users
  • Does not match: https://api.github.com/users/octocat

Wildcard match:

routes:
- https://api.github.com/*
  • Matches: https://api.github.com/users
  • Matches: https://api.github.com/users/octocat
  • Matches: https://api.github.com/repos/owner/repo
  • Does not match: http://api.github.com/users (different protocol)
  • Does not match: https://github.com/users (different hostname)

Route Precedence

When multiple routes could match a URL:

  1. Exact matches take precedence over wildcards
  2. More specific wildcards take precedence over less specific
  3. Routes are evaluated in the order configured

Example:

routes:
- https://api.github.com/users/octocat # Most specific (exact)
- https://api.github.com/users/* # More specific wildcard
- https://api.github.com/* # Less specific wildcard

Request to https://api.github.com/users/octocat matches the first route.

Adding Routes

Simple Routes

To add routes without custom headers, use the array format:

agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com
routes:
- https://api.github.com/*
- https://api.gitlab.com/*
- https://internal-api.company.com/v1/*

Routes with Headers

To add per-route authentication or custom headers:

agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com
routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'
accept: 'application/vnd.github.v3+json'

'https://internal-api.company.com/v1/*':
headers:
x-api-key: '${INTERNAL_API_KEY}'
x-forwarded-by: 'portal-connect-agent'

Set the referenced environment variables when starting the Agent:

docker run -d \
-e GITHUB_TOKEN="ghp_xxxxxxxxxxxx" \
-e INTERNAL_API_KEY="api-key-xxxxxxxxxxxx" \
europe-west1-docker.pkg.dev/spc-global-admin/spotify-portal-connect/portal-connect-agent:latest

Route Planning

Before adding routes, consider:

Scope: Use wildcards appropriately

  • Too broad: https://*/* (avoid - too permissive)
  • Too narrow: Individual endpoints (maintenance overhead)
  • Recommended: API version level https://api.example.com/v1/*

Authentication: Does the service require headers?

  • API keys
  • Bearer tokens
  • Custom authentication headers

Security: Should this route be accessible?

  • Apply principle of least privilege
  • Only add routes that are necessary

Adding a New Route

Step 1: Update configuration

Edit app-config.yaml:

agent:
routes:
- https://api.github.com/*
- https://api.example.com/* # New route

Or with headers:

agent:
routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'

'https://api.example.com/*': # New route
headers:
x-api-key: '${EXAMPLE_API_KEY}'

Step 2: Set environment variables (if using headers)

Update your deployment to include new environment variables:

# Docker
docker stop portal-connect-agent
docker rm portal-connect-agent
docker run -d \
--name portal-connect-agent \
-e GITHUB_TOKEN="..." \
-e EXAMPLE_API_KEY="new-api-key-here" \
-v $(pwd)/app-config.yaml:/app/config/app-config.yaml:ro \
europe-west1-docker.pkg.dev/spc-global-admin/spotify-portal-connect/portal-connect-agent:latest

# Docker Compose - update .env file, then:
docker-compose restart

# Kubernetes - update Secret, then:
kubectl create secret generic portal-connect-secrets \
--from-literal=example-api-key='new-api-key' \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart deployment/portal-connect-agent

Step 3: Restart Agent

# Docker
docker restart portal-connect-agent

# Docker Compose
docker-compose restart

# Kubernetes
kubectl rollout restart deployment/portal-connect-agent

Step 4: Verify route registration

Check logs:

docker logs portal-connect-agent | grep "Registered route"

Expected output:

Registered route: https://api.github.com/*
Registered route: https://api.example.com/*

Check metrics:

curl -s http://localhost:9466/metrics | grep agent_routes_registered

Expected output:

agent_routes_registered{} 2

Modifying Routes

Changing Route Patterns

To change a route pattern (e.g., from exact to wildcard):

Before:

routes:
- https://api.github.com/users

After:

routes:
- https://api.github.com/users/*

Restart the Agent to apply changes.

Adding Headers to Existing Route

To add headers to a route that previously had none:

Before:

routes:
- https://api.github.com/*

After:

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'

Don't forget to:

  1. Set the GITHUB_TOKEN environment variable
  2. Restart the Agent

Updating Headers

To change header values, update the configuration and environment variables:

Configuration change:

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'
accept: 'application/vnd.github.v3+json' # New header

If changing token values, update the environment variable and restart.

Converting Between Formats

From array to object format:

Before:

routes:
- https://api.github.com/*
- https://api.gitlab.com/*

After (adding headers to one route):

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'

'https://api.gitlab.com/*':
headers:
private-token: '${GITLAB_TOKEN}'
Format Consistency

You cannot mix array and object formats. Choose one format for all routes.

Route Security Best Practices

Principle of Least Privilege

Only add routes that are absolutely necessary:

Bad - too permissive:

routes:
- https://*/*
- http://*/*

Good - specific routes:

routes:
- https://api.github.com/*
- https://internal-api.company.com/v1/*

Credential Management

Never hardcode credentials:

Bad - credentials in config file:

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ghp_AbCdEfGhIjKlMnOpQrStUvWxYz'

Good - use environment variables:

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'

Credential Rotation

When rotating API credentials:

  1. Generate new credential
  2. Update environment variable or secret
  3. Restart Agent
  4. Revoke old credential

Kubernetes example:

# Update secret with new token
kubectl create secret generic portal-connect-secrets \
--from-literal=github-token='new-token-here' \
--dry-run=client -o yaml | kubectl apply -f -

# Rolling restart to pick up new secret
kubectl rollout restart deployment/portal-connect-agent

# Verify Agent is healthy
kubectl get pods -l app=portal-connect-agent

# Once verified, revoke old token in GitHub

Common Route Patterns

GitHub API

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'
accept: 'application/vnd.github.v3+json'

GitLab API

routes:
'https://gitlab.com/api/*':
headers:
private-token: '${GITLAB_TOKEN}'

Internal REST API

routes:
'https://internal-api.company.com/v1/*':
headers:
x-api-key: '${INTERNAL_API_KEY}'
x-forwarded-by: 'portal-connect-agent'

Prometheus Metrics Endpoint

routes:
- https://prometheus.company.com/api/v1/*

Health Check Endpoints

routes:
- https://service1.company.com/health
- https://service2.company.com/health
- https://service3.company.com/healthz

Multiple API Versions

routes:
- https://api.company.com/v1/*
- https://api.company.com/v2/*

Troubleshooting Routes

Route Not Registered

Symptom: Route doesn't appear in logs or metrics

Diagnostic:

  1. Check configuration syntax:

    cat app-config.yaml
  2. Validate YAML:

    python -c "import yaml; yaml.safe_load(open('app-config.yaml'))"
  3. Check for configuration errors in logs:

    docker logs portal-connect-agent | grep -i error

Solutions:

  • Fix YAML syntax errors
  • Ensure routes are under agent.routes key
  • Restart Agent after configuration changes

Requests Not Matching Route

Symptom: Requests returning 404 or not being processed

Diagnostic:

  • Verify exact URL being requested
  • Check route pattern matches the URL
  • Review logs for rejected requests

Common mismatches:

  • Protocol mismatch: Route is https:// but request is http://
  • Hostname mismatch: Different subdomain or domain
  • Path mismatch: Wildcard doesn't cover the requested path

Solutions:

  • Adjust route pattern to match intended URLs
  • Use wildcards appropriately
  • Add specific routes for edge cases

Headers Not Being Sent

Symptom: External service returns authentication errors

Diagnostic:

  1. Check environment variables are set:

    docker exec portal-connect-agent env | grep TOKEN
  2. Verify header syntax in configuration

  3. Check logs for request details (debug level)

Solutions:

  • Set missing environment variables
  • Fix header syntax in configuration
  • Ensure variable names match between config and environment

Route with Wrong Credentials

Symptom: Authentication failures for specific route

Diagnostic:

Test the credential directly:

curl -H "Authorization: Bearer ${GITHUB_TOKEN}" https://api.github.com/user

Solutions:

  • Update credential in environment variable
  • Verify credential is valid and not expired
  • Check credential has necessary permissions for the API

Monitoring and Operations

Metrics Endpoint

The Agent exposes Prometheus metrics on port 9466 (configurable via AGENT_METRICS_PORT).

Metrics Port

The metrics endpoint is optional and only needs to be accessible from your monitoring infrastructure within your network. It does not require inbound access from the internet - the Agent still only makes outbound connections for its primary function.

Accessing Metrics:

curl http://localhost:9466/metrics

Key Metrics

Connection Metrics

agent_websocket_connected

  • Type: Gauge
  • Description: WebSocket connection state (0 = disconnected, 1 = connected)
  • Use: Monitor gateway connectivity
agent_websocket_connected{} 1

agent_websocket_reconnections_total

  • Type: Counter
  • Description: Total number of reconnection attempts
  • Use: Track connection stability

Request Metrics

agent_http_requests_total

  • Type: Counter
  • Labels: method, status_code, route
  • Description: Total HTTP requests processed
  • Use: Track request volume and status codes
agent_http_requests_total{method="GET",status_code="200",route="https://api.github.com/*"} 152
agent_http_requests_total{method="POST",status_code="201",route="https://api.github.com/*"} 23

agent_http_request_duration_seconds

  • Type: Histogram
  • Labels: method, route
  • Description: HTTP request duration distribution
  • Use: Monitor request latency
agent_http_request_duration_seconds_bucket{method="GET",route="https://api.github.com/*",le="0.1"} 120
agent_http_request_duration_seconds_bucket{method="GET",route="https://api.github.com/*",le="0.5"} 145
agent_http_request_duration_seconds_sum{method="GET",route="https://api.github.com/*"} 18.5
agent_http_request_duration_seconds_count{method="GET",route="https://api.github.com/*"} 152

agent_http_request_errors_total

  • Type: Counter
  • Labels: method, route, error_type
  • Description: Total HTTP request errors
  • Use: Monitor error rates
agent_http_request_errors_total{method="GET",route="https://api.github.com/*",error_type="timeout"} 3
agent_http_request_errors_total{method="GET",route="https://api.github.com/*",error_type="network"} 1

Route Metrics

agent_routes_registered

  • Type: Gauge
  • Description: Number of routes currently registered
  • Use: Verify route configuration
agent_routes_registered{} 5

Alerting

Recommended Alerts

Critical Alerts

WebSocket Disconnected:

agent_websocket_connected{} == 0
  • Threshold: Alert if disconnected for > 5 minutes
  • Action: Check network connectivity, Portal status, certificate validity

Certificate Expiring Soon:

agent_certificate_expiry_seconds{} < 604800  # 7 days
  • Threshold: Alert 7 days before expiration
  • Action: Generate and register new certificate

High Error Rate:

(
sum(rate(agent_http_request_errors_total[5m]))
/
sum(rate(agent_http_requests_total[5m]))
) > 0.05 # 5% error rate
  • Threshold: > 5% error rate over 5 minutes
  • Action: Investigate logs, check external service status

Agent Down:

up{job="portal-connect-agent"} == 0
  • Threshold: Alert immediately
  • Action: Restart Agent, check logs for crash reason

Warning Alerts

Elevated Error Rate:

(
sum(rate(agent_http_request_errors_total[15m]))
/
sum(rate(agent_http_requests_total[15m]))
) > 0.01 # 1% error rate
  • Threshold: > 1% error rate over 15 minutes
  • Action: Monitor, investigate if sustained

Slow Requests:

histogram_quantile(0.95,
rate(agent_http_request_duration_seconds_bucket[5m])
) > 5 # 5 seconds
  • Threshold: P95 latency > 5 seconds
  • Action: Check external service performance

Frequent Reconnections:

rate(agent_websocket_reconnections_total[15m]) > 0.1
  • Threshold: > 0.1 reconnections/second
  • Action: Investigate network stability

Monitoring WebSocket Connection

The Agent automatically reconnects if the WebSocket connection drops:

  • Initial retry: 1 second
  • Maximum retry interval: 60 seconds
  • Uses exponential backoff with jitter

Monitor connection stability:

# Count disconnection events in last hour
docker logs --since 1h portal-connect-agent | grep -c "WebSocket disconnected"

Monitoring Request Success

View request metrics:

curl -s http://localhost:9466/metrics | grep agent_http_requests_total

Calculate error rate:

# View error count
curl -s http://localhost:9466/metrics | grep agent_http_request_errors_total

Health Checks

Docker Health Check

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:9466/metrics || exit 1

Kubernetes Probes

livenessProbe:
httpGet:
path: /metrics
port: 9466
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3

readinessProbe:
httpGet:
path: /metrics
port: 9466
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

Troubleshooting

Connection Issues

WebSocket Connection Fails

Symptoms:

  • agent_websocket_connected{} == 0
  • Logs show WebSocket connection failed or Connection refused

Diagnostic Steps:

  1. Verify WebSocket URL:

    grep brokerUrl app-config.yaml
  2. Test network connectivity:

    curl -v wss://broker.${subdomain}.spotifyportal.com
  3. Check firewall rules:

    • Ensure outbound WSS (port 443 or 8080) allowed
    • Test from Agent host
  4. Verify certificates:

    openssl x509 -in /certs/client.crt -noout -dates

Solutions:

  • Fix WebSocket URL in configuration
  • Update firewall rules to allow outbound WSS
  • Renew expired certificates
  • Contact Spotify to verify Portal is operational

Frequent Disconnections

Symptoms:

  • agent_websocket_reconnections_total increasing
  • Logs show repeated disconnect/reconnect cycles

Diagnostic Steps:

  1. Check connection stability:

    # Monitor reconnections over time
    curl -s http://localhost:9466/metrics | grep reconnections
  2. Review network logs:

    • Check for intermittent network issues
    • Look for firewall or proxy interruptions
  3. Verify Portal health:

    • Contact Spotify support
    • Check for Portal maintenance windows

Solutions:

  • Resolve network stability issues
  • Adjust firewall/proxy timeouts for WebSocket connections
  • Coordinate with Spotify on Portal issues

SSL Inspection / Corporate Proxy Intercepting TLS

Symptoms:

  • Repeated reconnection failures in logs
  • Error message similar to: Error: self-signed certificate in certificate chain
  • agent_websocket_connected{} == 0 even though broker URL and network access are correct
  • Other outbound HTTPS requests from the Agent may also fail

Diagnostic Steps:

  1. Check whether your organization performs SSL/TLS inspection

    • Firewalls such as Palo Alto, Zscaler, Cisco, or Blue Coat may intercept and re-sign outbound TLS connections
    • If so, Portal Connect's WebSocket connection will be re-signed using a corporate CA that is not trusted by Node.js by default
  2. Inspect Agent logs for certificate errors:

    docker logs portal-connect-agent | grep -i "self-signed"

    or:

    kubectl logs deployment/portal-connect-agent | grep -i certificate
  3. Verify whether Node.js trusts your corporate CA

    Inside the Agent container:

    docker exec portal-connect-agent node -p "require('tls').rootCertificates"

    If your corporate CA is not listed, Node.js will reject the connection.

  4. Ensure you have the correct TLS inspection certificates

    • Export the root + intermediate CA certificates from your firewall
    • Certificates must be PEM-encoded:
      -----BEGIN CERTIFICATE-----
      ...
      -----END CERTIFICATE-----

Solutions:

  1. Create a CA bundle containing your corporate inspection certificates:

    cat root-ca.pem intermediate-ca.pem > custom-ca-bundle.pem
  2. Mount the CA bundle into the Agent container

    Example:

    -v ./certs:/certs:ro
  3. Tell Node.js to trust your corporate CA using NODE_EXTRA_CA_CERTS

    Docker run:

    -e NODE_EXTRA_CA_CERTS=/certs/custom-ca-bundle.pem

    Docker Compose:

    environment:
    - NODE_EXTRA_CA_CERTS=/certs/custom-ca-bundle.pem

    Kubernetes:

    env:
    - name: NODE_EXTRA_CA_CERTS
    value: /etc/portal-connect/custom-ca-bundle.pem
  4. Restart or redeploy the Agent:

    docker restart portal-connect-agent

    or:

    kubectl rollout restart deployment/portal-connect-agent
  5. Verify WebSocket connectivity:

    curl http://localhost:9466/metrics | grep agent_websocket_connected

    Expected:

    agent_websocket_connected{} 1

Request Failures

All Requests Failing

Symptoms:

  • High error rate across all routes
  • Logs show consistent request failures

Diagnostic Steps:

  1. Check Agent connectivity:

    docker logs portal-connect-agent | grep "WebSocket connected"
  2. Verify routes are registered:

    curl http://gateway:8080/routes
  3. Test external service directly from Agent host:

    docker exec portal-connect-agent curl -v https://api.github.com

Solutions:

  • Restore WebSocket connection
  • Fix route configuration and restart Agent
  • Resolve network access to external services

Specific Route Failing

Symptoms:

  • Errors for specific route/URL pattern
  • Other routes working normally

Diagnostic Steps:

  1. Check route configuration:

    grep -A5 "routes:" app-config.yaml
  2. Test service directly:

    curl -v https://failing-service.com/endpoint
  3. Check service-specific authentication:

    • Verify headers configured correctly
    • Test with actual credentials

Solutions:

  • Fix route pattern (wildcard, exact match)
  • Update authentication headers
  • Verify external service is operational

Timeout Errors

Symptoms:

  • Logs show Network timeout or Request timeout
  • Slow response times

Diagnostic Steps:

  1. Check request latency:

    curl -s http://localhost:9466/metrics | grep duration_seconds
  2. Test external service response time:

    time curl https://external-service.com/endpoint
  3. Review Agent resource usage:

    docker stats portal-connect-agent

Solutions:

  • Increase timeout configuration (if configurable)
  • Optimize external service performance
  • Scale Agent resources if CPU/memory constrained

Certificate Issues

Certificate Validation Fails

Symptoms:

  • mTLS certificate validation failed in logs
  • Agent fails to start

Diagnostic Steps:

  1. Verify certificate files exist:

    ls -la /certs/
  2. Check certificate validity:

    openssl x509 -in /certs/portal-connect-client-<timestamp>.crt \
    -noout -dates -subject -issuer
  3. Verify certificate and key match:

    openssl x509 -in /certs/portal-connect-client-<timestamp>.crt \
    -noout -modulus | openssl md5
    openssl rsa -in /certs/portal-connect-client-<timestamp>.key \
    -noout -modulus | openssl md5
  4. Check file permissions:

    stat /certs/portal-connect-client-<timestamp>.crt \
    /certs/portal-connect-client-<timestamp>.key

Solutions:

  • Generate new certificates
  • Fix file permissions (readable by container user)
  • Ensure certificate is registered with Spotify
  • Verify certificate and key are matching pair

Certificate Expired

Symptoms:

  • Certificate expired in logs
  • agent_certificate_expiry_seconds negative or very low

Diagnostic:

openssl x509 -in /certs/portal-connect-client-<timestamp>.crt -noout -enddate

Solutions:

  1. Generate new certificate
  2. Register with Spotify
  3. Update certificate files
  4. Restart Agent

Configuration Issues

Configuration File Not Found

Symptoms:

  • Configuration file not found or ENOENT error
  • Agent exits immediately

Diagnostic Steps:

  1. Check CONFIG_PATH:

    docker inspect portal-connect-agent | grep CONFIG_PATH
  2. Verify volume mount:

    docker inspect portal-connect-agent | grep -A10 Mounts
  3. Check file exists in container:

    docker exec portal-connect-agent ls -la /app/config/

Solutions:

  • Fix CONFIG_PATH environment variable
  • Fix volume mount path
  • Verify file exists on host at mount source

Invalid Configuration Syntax

Symptoms:

  • Invalid configuration or YAML parse errors
  • Agent fails to start

Diagnostic Steps:

  1. Validate YAML syntax:

    python -c "import yaml; yaml.safe_load(open('app-config.yaml'))"
  2. Check for common errors:

    • Incorrect indentation
    • Missing required fields
    • Invalid route patterns

Solutions:

  • Fix YAML syntax errors
  • Validate against configuration reference
  • Use YAML linter

Environment Variable Not Substituted

Symptoms:

  • Headers contain literal ${VAR_NAME}
  • Authentication fails for external services

Diagnostic Steps:

  1. Check environment variables are set:

    docker exec portal-connect-agent env | grep TOKEN
  2. Review configuration:

    docker exec portal-connect-agent cat /app/config/app-config.yaml

Solutions:

  • Set missing environment variables when starting container
  • Fix environment variable name mismatch
  • Use correct syntax: ${VAR_NAME} for env vars, ${VAR_NAME_FILE} for file-based

Resource Issues

High CPU Usage

Symptoms:

  • Container using > 80% CPU consistently
  • Slow request processing

Diagnostic:

docker stats portal-connect-agent

Solutions:

  • Increase CPU limits
  • Scale horizontally (add more Agent replicas)
  • Investigate unusually high request volume

High Memory Usage

Symptoms:

  • Container approaching memory limit
  • OOMKilled events (Kubernetes)

Diagnostic:

docker stats portal-connect-agent
kubectl describe pod portal-connect-agent-xxx

Solutions:

  • Increase memory limits
  • Check for memory leaks (contact Spotify support)
  • Restart Agent to clear memory

Container Keeps Restarting

Symptoms:

  • docker ps shows frequent restarts
  • Pod in CrashLoopBackOff (Kubernetes)

Diagnostic Steps:

  1. Check logs:

    docker logs portal-connect-agent
  2. Check exit code:

    docker inspect portal-connect-agent | grep ExitCode

Solutions:

  • Fix configuration errors causing startup failure
  • Resolve certificate validation issues
  • Check resource limits not too restrictive
  • Review logs for specific error messages

Configuration Reference

This reference documents all configuration options for the Portal Connect Agent.

Configuration File Format

The Portal Connect Agent uses YAML format for configuration. The default configuration file location is app-config.yaml, but you can specify a custom path using the CONFIG_PATH environment variable.

Configuration Structure

agent:
brokerUrl: string (required)
routes: array or object (required)
mtls: object (optional, enabled by default)

Configuration Options

agent.brokerUrl

  • Type: String (required)
  • Description: WebSocket URL to connect the Agent to your Portal instance
  • Format: wss://broker.${subdomain}.spotifyportal.com

Example:

agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com

Notes:

  • Must use wss:// protocol (secure WebSocket)
  • Connection URL provided by Spotify
  • Do not modify the path component

agent.routes

  • Type: Array or Object (required)
  • Description: Defines which external URLs the Agent can access

Simple Array Format

Use this format for routes without custom headers:

agent:
routes:
- https://api.github.com/*
- https://internal-api.company.com/v1/*
- https://status.company.com/health

Advanced Object Format

Use this format to specify per-route headers:

agent:
routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'

'https://internal-api.company.com/v1/*':
headers:
x-api-key: '${INTERNAL_API_KEY}'
x-forwarded-by: 'portal-connect-agent'

Route Patterns

Exact match:

routes:
- https://api.example.com/v1/users
  • Matches only: https://api.example.com/v1/users

Wildcard match:

routes:
- https://api.example.com/v1/*
  • Matches:
    • https://api.example.com/v1/users
    • https://api.example.com/v1/users/123
    • https://api.example.com/v1/anything
  • Does not match:
    • https://api.example.com/v2/users (different path)
    • http://api.example.com/v1/users (different protocol)

Route Requirements

  • Must include protocol (http:// or https://)
  • Must include hostname
  • Path is optional but recommended
  • Wildcards (*) only supported at end of path
  • No query parameters or fragments in route patterns

Valid routes:

routes:
- https://api.example.com/*
- https://api.example.com/v1/*
- https://api.example.com/v1/users
- http://legacy-api.company.com/health

Invalid routes:

routes:
- api.example.com/* # Missing protocol
- https://*.example.com/* # Wildcard in hostname not supported
- https://api.example.com/*/users # Wildcard in middle of path

agent.mtls

  • Type: Object (optional, enabled by default)
  • Description: Mutual TLS (mTLS) configuration for secure authentication

agent.mtls.enabled

  • Type: Boolean (optional)
  • Default: true
  • Description: Enable or disable mTLS authentication
Production Use

mTLS is required for production use. Only disable for local development.

agent:
mtls:
enabled: true

agent.mtls.clientCertPath

  • Type: String (required when mTLS enabled)
  • Description: Path to client certificate file in PEM format

Example:

agent:
mtls:
clientCertPath: /etc/ssl/certs/portal-connect-client.crt

Notes:

  • Must be PEM-encoded X.509 certificate
  • Maximum certificate lifespan: 180 days
  • Certificate must be registered with Spotify

agent.mtls.clientKeyPath

  • Type: String (required when mTLS enabled)
  • Description: Path to private key file in PEM format

Example:

agent:
mtls:
clientKeyPath: /etc/ssl/private/portal-connect-client.key

Notes:

  • Must be PEM-encoded RSA or ECDSA private key
  • For ECDSA: P-256 or P-384 elliptical curve (P-384 recommended)
  • For RSA: Minimum 2048-bit key size (4096-bit recommended)
  • Keep file secure with restricted permissions (400 or 600)

agent.mtls.validateOnStartup

  • Type: Boolean (optional)
  • Default: true
  • Description: Validate certificates when Agent starts

Example:

agent:
mtls:
validateOnStartup: true

Notes:

  • Recommended to keep enabled
  • Startup will fail if certificates are invalid or expired
  • Helps catch certificate issues early

Environment Variables

CONFIG_PATH

  • Type: String
  • Default: ./app-config.yaml
  • Description: Path to the Agent configuration file

Example:

-e CONFIG_PATH=/app/config/app-config.yaml

AGENT_METRICS_PORT

  • Type: Integer
  • Default: 9466
  • Description: Port for Prometheus metrics endpoint

Example:

-e AGENT_METRICS_PORT=9466

LOG_LEVEL

  • Type: String
  • Default: info
  • Valid values: debug, info, warn, error
  • Description: Logging verbosity level

Example:

-e LOG_LEVEL=debug

Environment Variable Substitution

Configuration files support environment variable substitution for sensitive values.

Using Environment Variables

Reference environment variables in configuration with ${VAR_NAME}:

agent:
routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'

Set the environment variable when running the container:

docker run -d \
-e GITHUB_TOKEN="ghp_xxxxxxxxxxxx" \
europe-west1-docker.pkg.dev/spc-global-admin/spotify-portal-connect/portal-connect-agent:latest

File-Based Secrets

For secrets stored in files (e.g., Kubernetes secrets), use ${VAR_NAME_FILE}:

agent:
routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN_FILE}'

The Agent will read the secret from the file path:

docker run -d \
-e GITHUB_TOKEN_FILE="/run/secrets/github_token" \
-v /path/to/secrets:/run/secrets \
europe-west1-docker.pkg.dev/spc-global-admin/spotify-portal-connect/portal-connect-agent:latest

Complete Configuration Examples

Minimal Configuration

agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com

routes:
- https://api.github.com/*

mtls:
clientCertPath: /certs/client.crt
clientKeyPath: /certs/client.key

Configuration with Multiple Routes

agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com

routes:
- https://api.github.com/*
- https://api.gitlab.com/*
- https://internal-api.company.com/v1/*
- https://metrics.company.com/prometheus/*
- https://legacy-service.company.com/health

mtls:
enabled: true
clientCertPath: /etc/ssl/certs/portal-connect-client.crt
clientKeyPath: /etc/ssl/private/portal-connect-client.key
validateOnStartup: true

Configuration with Authentication Headers

agent:
brokerUrl: wss://broker.${subdomain}.spotifyportal.com

routes:
'https://api.github.com/*':
headers:
authorization: 'Bearer ${GITHUB_TOKEN}'
accept: 'application/vnd.github.v3+json'

'https://api.gitlab.com/*':
headers:
private-token: '${GITLAB_TOKEN}'

'https://internal-api.company.com/v1/*':
headers:
x-api-key: '${INTERNAL_API_KEY}'
x-forwarded-by: 'portal-connect-agent'
x-environment: 'production'

mtls:
clientCertPath: /certs/client.crt
clientKeyPath: /certs/client.key