Get started with portal
Create your first portal web server and expose the httpbin API on it.
Portal is a developer portal for Solo Enterprise for kgateway that lets API consumers discover, browse, and interact with APIs. The portal renders an API catalog from OpenAPI specifications and provides self-service features, including team management, app registration, and API key provisioning.
About this guide
In this guide, you learn how to set up your first portal web server and expose the httpbin API in your API catalog. To interact with the portal, you create a frontend app that allows you to view the APIs that are exposed in the API catalog.
Before you begin
Follow the Get started guide to install Solo Enterprise for kgateway.
Set up a portal
Install the portal controller and set up your first portal web server.
Get a portal license and set it as an environment variable. If you do not have one, contact an account representative.
export PORTAL_LICENSE_KEY=<portal-license-key>Install the portal CRDs.
helm upgrade -i portal-crds \ oci://us-docker.pkg.dev/solo-public/enterprise-kgateway/charts/portal-crds \ --version 2.2.4 \ --namespace portal-system \ --create-namespaceInstall the portal controller.
helm upgrade -i portal \ oci://us-docker.pkg.dev/solo-public/enterprise-kgateway/charts/portal \ --version 2.2.4 \ --namespace portal-system \ --set licensing.licenseKey=$PORTAL_LICENSE_KEYVerify that the controller is up and running. The controller watches Portal custom resources.
kubectl get pods -n portal-systemExample output:
NAME READY STATUS RESTARTS AGE portal-controller-5b98557f9b-kz56c 1/1 Running 0 45sCreate a PortalParameters resource.
PortalParameters control the operational configuration of the portal web server, such as the data store that you want to use. For testing purposes, you can use the built-in container storage that is used in this example. Note that data is removed when the portal web server restarts. To use a persistent data storage solution, such as PostgresQL, see the Portal database install guide.
kubectl apply -f- <<EOF apiVersion: portal.solo.io/v1alpha1 kind: PortalParameters metadata: name: portal-params namespace: default spec: store: memory: {} EOFCreate the portal.
After you create the resource, the portal controller spins up a portal web server for you. The portal web server represents the portal backend. To interact with your portal and share APIs with users, you must create a frontend app for your portal as shown in later steps of this tutorial. Note that no APIProducts are referenced in the portal yet. You populate APIProducts later.
kubectl apply -f- <<EOF apiVersion: portal.solo.io/v1alpha1 kind: Portal metadata: name: my-portal namespace: default spec: parametersRef: name: portal-params visibility: public: true apiProductRefs: [] EOFSetting Description parametersRefReferences the PortalParametersresource that you created earlier.visibility.publicWhen set to true, the API catalog in the portal is accessible without authentication.apiProductRefsList of ApiProduct references to include in the API catalog. In this example, no APIProducts are referenced yet. You populate the portal catalog later. Verify that a portal web server is created for you.
kubectl get deployment portal-my-portal -n default kubectl get service portal-my-portal -n defaultExample output:
NAME READY UP-TO-DATE AVAILABLE AGE portal-my-portal 1/1 1 1 2m51s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE portal-my-portal ClusterIP 10.96.78.9 <none> 8080/TCP 2m51s
Expose the portal
Create a Gateway for your portal web server that serves requests for the portal.example.com sample domain.
Create a Gateway.
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: portal-gateway namespace: default spec: gatewayClassName: enterprise-kgateway listeners: - name: http port: 8080 protocol: HTTP allowedRoutes: namespaces: from: All EOFCreate an HTTPRoute to route traffic to the portal. The portal backend exposes the following
/v1/endpoints./v1/api-products: API catalog (public)/v1/metadata: Credential validation/v1/me,/v1/users: User management (requires auth)/v1/teams: Team management (requires auth)/v1/apps: App registration and API key provisioning (requires auth)/v1/subscriptions: API subscription management (requires auth)/v1/oauth-credentials: OAuth credential management (requires auth)/v1/login,/v1/logout: OIDC redirect endpoints (when using OIDC auth)
For a minimal public catalog, expose only the
/v1/api-productsand/v1/metadatapaths. To keep the configuration simple, you can expose all/v1/paths as shown in this exmaple.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: my-portal-backend namespace: default spec: parentRefs: - name: portal-gateway namespace: default hostnames: - "portal.example.com" rules: - filters: - type: CORS cors: allowHeaders: - "*" allowMethods: - GET - POST - PUT - DELETE - OPTIONS allowOrigins: - "*" matches: - path: type: PathPrefix value: /v1/ backendRefs: - name: portal-my-portal namespace: default port: 8080 EOF
Deploy the portal frontend
To interact with the portal backend and share APIs with users, create a frontend app for your portal. You can use your own frontend app or deploy the app that is provided by Solo.io as shown in this tutorial.
Create the portal frontend. Make sure to set the
VITE_PORTAL_SERVER_URLenvironment variable to the hostname and path prefix that you configured in the Gateway and HTTPRoute for your portal backend.kubectl apply -f- <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: portal-ui namespace: default --- apiVersion: apps/v1 kind: Deployment metadata: name: portal-ui namespace: default spec: replicas: 1 selector: matchLabels: app: portal-ui template: metadata: labels: app: portal-ui spec: serviceAccountName: portal-ui securityContext: runAsNonRoot: true runAsUser: 10101 containers: - name: portal-ui image: gcr.io/solo-public/docs/portal-frontend:v0.1.8 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 4000 protocol: TCP env: - name: VITE_PORTAL_SERVER_URL value: "http://portal.example.com:8080/v1" livenessProbe: httpGet: path: / port: http initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: / port: http initialDelaySeconds: 5 periodSeconds: 5 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL --- apiVersion: v1 kind: Service metadata: name: portal-ui namespace: default spec: type: ClusterIP ports: - port: 4000 targetPort: http protocol: TCP name: http selector: app: portal-ui EOFThe demo frontend displays a Login button by default. Without any OIDC configuration, anAccess Issueserror is deplayed when you click the button. This is expected. The public API catalog works without authentication. To enable login, you need to deploy an identity provider, such Keycloak, and set theVITE_APPLIED_OIDC_AUTH_CODE_CONFIG,VITE_OIDC_AUTH_CODE_CONFIG_CALLBACK_PATH, andVITE_OIDC_AUTH_CODE_CONFIG_LOGOUT_PATHenvironment variables on the frontend.Wait for the frontend app to be deployed.
kubectl rollout status deploy/portal-uiCreate an HTTPRoute to route traffic to the frontend.
The frontend route uses a
/catch-all prefix path. Note that the Kubernetes Gateway API evaluates routes by specificity. Because of that, more specific/v1/*backend routes take precedence. The frontend route serves all other paths, including the UI assets orindex.htmlfile.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: my-portal-frontend namespace: default spec: parentRefs: - name: portal-gateway namespace: default hostnames: - "portal.example.com" rules: - filters: - type: CORS cors: allowHeaders: - "*" allowMethods: - GET - POST - PUT - DELETE - OPTIONS allowOrigins: - "*" matches: - path: type: PathPrefix value: / backendRefs: - name: portal-ui namespace: default port: 4000 EOFPort-forward the gateway on port 8080
kubectl port-forward -n default svc/portal-gateway 8080:8080Edit the
/etc/hostsfile on your local machine to map theportal.example.comdomain to localhost. This step is required in local test setups to access the portal frontend from your web browser. If you used a registered domain that you own to expose the portal, this step is not required and can be skipped.sudo nano /etc/hostsAdd the following entry:
127.0.0.1 portal.example.comIn a web browser, open the portal UI on the domain that you set up, such as http://portal.example.com:8080/. You see the portal UI with an empty API catalog.


Add API products
Now that you have a portal up and running, you can start exposing APIs on it. This example exposes the httpbin API as an API product.
Deploy the httpbin sample app.
kubectl apply -f- <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: httpbin namespace: default --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin namespace: default spec: replicas: 1 selector: matchLabels: app: httpbin template: metadata: labels: app: httpbin spec: serviceAccountName: httpbin containers: - name: httpbin image: mccutchen/go-httpbin:v2.15.0 ports: - containerPort: 8080 env: - name: PORT value: "8080" resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 128Mi --- apiVersion: v1 kind: Service metadata: name: httpbin namespace: default spec: type: ClusterIP ports: - port: 8080 targetPort: 8080 name: http selector: app: httpbin EOFCreate an HTTPRoute for the httpbin app. The app is exposed under the
api.example.comsample domain. You can use this domain as an umbrella for all your APIs that you want to expose in the portal. TheURLRewritefilter removes the/httpbinprefix before forwarding the request to the backend. For example, a request to/httpbin/getis rewritten to/getso that the httpbin service can serve it properly.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httpbin-route namespace: default spec: parentRefs: - name: portal-gateway namespace: default hostnames: - "api.example.com" rules: - matches: - path: type: PathPrefix value: /httpbin filters: - type: URLRewrite urlRewrite: path: type: ReplacePrefixMatch replacePrefixMatch: / backendRefs: - name: httpbin port: 8080 EOFCreate an ApiDoc.
An ApiDoc describes and pairs the OpenAPI schema with the backend service that serves this schema. To pair an ApiDoc with a service, you use the
servedByfield. The following ApiDoc describes the/get,/headers, and/status/{code}API endpoints of the httpbin API. Note that the API endpoints are manually added to the ApiDoc. You can also fetch the schema from a URL or an endpoint that is exposed by the service. For more information, see Create ApiDocs.kubectl apply -f- <<EOF apiVersion: portal.solo.io/v1alpha1 kind: ApiDoc metadata: name: httpbin-apidoc namespace: default spec: source: manual: content: | { "openapi": "3.0.0", "info": { "title": "HTTPBin API", "version": "1.0.0", "description": "A simple HTTP request/response service" }, "paths": { "/get": { "get": { "summary": "Returns GET data", "responses": { "200": { "description": "Successful response with request details" } } } }, "/headers": { "get": { "summary": "Returns request headers", "responses": { "200": { "description": "Successful response with headers" } } } }, "/status/{code}": { "get": { "summary": "Returns given HTTP status code", "parameters": [ { "name": "code", "in": "path", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "Successful response" } } } } } } servedBy: name: httpbin namespace: default port: 8080 EOFCreate an ApiProduct.
An ApiProduct is a logical API product that can describe one or more versions of an API that you want to expose in the portal API catalog. Each version references the HTTPRoute that serves this version of the API. The portal controller automatically discovers the paired ApiDocs resource to display available API endpoints for that version in the portal frontend. When you create an ApiProduct, version-specific schema and metadata information is automatically stitched together into one schema and added to another ApiDocs resource.
kubectl apply -f- <<EOF apiVersion: portal.solo.io/v1alpha1 kind: ApiProduct metadata: name: httpbin-api namespace: default spec: id: httpbin displayName: "HTTPBin API" customMetadata: category: "Utilities" versions: - name: v1 targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin-route openApiMetadata: title: "HTTPBin API" description: "A simple HTTP request/response service for testing" EOFAdd the ApiProduct to the portal.
kubectl apply -f- <<EOF apiVersion: portal.solo.io/v1alpha1 kind: Portal metadata: name: my-portal namespace: default spec: parametersRef: name: portal-params visibility: public: true apiProductRefs: - name: httpbin-api EOFVerify that all portal resources are ready. Note that you see two ApiDocs resources, the
httpbin-apidocone that you created andhttpbin-api-v1that was automatically created by the portal controller and contains the stitched schema for all versions of the httpbin API that you want to expose.kubectl get apidoc,apiproduct,portal,portalconfig,portalparameters -n defaultExample output:
NAME SOURCE READY apidoc.portal.solo.io/httpbin-apidoc Manual True apidoc.portal.solo.io/httpbin-api-v1 Stitched True NAME ID DISPLAY NAME READY apiproduct.portal.solo.io/httpbin-api httpbin HTTPBin API True NAME PROGRAMMED portal.portal.solo.io/my-portal TrueEdit the
/etc/hostsfile again to map theapi.example.comsample domain to localhost on your local machine. This step is required in local test setups so that you can access the httpbin API via the portal frontend app in your web browser. If you used a registered domain, this step is not required and can be skipped.sudo nano /etc/hostsAdd the following entry:
127.0.0.1 portal.example.com api.example.comOpen the portal UI such as http://portal.example.com:8080/ in a browser and select APIs from the navbar. Verify that you see the HTTPBin API in the portal catalog.


Click the HTTPBin API card to drill into the API. By default, the API opens the Redocly view. You can optionally switch to the Swagger view to try out the API endpoints that you exposed.


Next
Now that you have your first API exposed on your portal, check out the following guides to further build out your portal.