The question comes up in almost every OKE engagement we run: the code lives on GitHub, the cluster lives on OCI, so what should the pipeline look like? Oracle has its own answer in the OCI DevOps service, and plenty of enterprises still carry a Jenkins estate, but for teams already paying for GitHub the gravitational pull of Actions is hard to argue with. The workflow file sits next to the code, the pull request is the unit of change, and the marketplace has an action for nearly everything, including official Oracle actions for talking to OCI.
This article is part of our series on application development and serverless on OCI. We will cover why GitHub Actions fits OKE pipelines, what must exist before the first workflow runs, the three ways a runner can authenticate to OCI and why short lived credentials should win, a numbered setup walkthrough, a complete example workflow, environments and approvals, rollback, secrets handling, how Actions compares with OCI DevOps and Jenkins, and the failures we get called in to fix.
Why GitHub Actions for OKE pipelines
Three properties make Actions a strong default for OKE delivery. First, proximity to the change: the workflow triggers on the same push or pull request event that carries the code, so there is no synchronisation between a source system and a separate CI server, and no webhook plumbing to maintain. Second, the ecosystem: Docker login, Buildx, Helm, kubectl, Trivy scanning, and Oracle's own published actions are all single line additions to a workflow, which means the pipeline definition stays short enough to review properly in a pull request. Third, governance that developers actually use: GitHub environments give you protected deployment targets, required reviewers, and scoped secrets without buying or building anything.
The honest caveats: GitHub hosted runners live outside your tenancy, so a cluster with a fully private API endpoint needs either self hosted runners inside the VCN or a bastion path. And minutes on hosted runners are a real line item for busy monorepos. Neither caveat changes the recommendation for most teams; both change the design, so decide them up front.
Prerequisites
Before the first workflow runs, four things need to exist. You need an OKE cluster with a node pool sized for your workloads and an API endpoint the runner can reach; for hosted runners that means a public endpoint locked down with network security groups, for private endpoints it means self hosted runners. You need an OCIR repository in the region close to the cluster, created ahead of time so that image pushes do not depend on implicit repository creation rights. You need an IAM identity for the pipeline: a user, a dynamic group, or an identity domain trust, with policies that allow exactly two things, pushing images to the repos in one compartment and reading the cluster details needed to generate a kubeconfig. And you need a Kubernetes namespace with RBAC for the deployer, because cluster admin in a pipeline is a standing invitation to disaster.
One OCIR detail trips up nearly everyone the first time: the registry login username is your tenancy's object storage namespace followed by a slash and the username, and the password is an auth token, never the console password. Budget ten minutes for this and you will save an afternoon.
How the runner authenticates to OCI
Everything in this pipeline hinges on one question: how does a GitHub runner prove to OCI that it is allowed to push images and deploy to the cluster? There are three answers, and they are not equal.
API keys for a service user
The traditional pattern: create a dedicated IAM user, generate an API signing key, store the private key and its fingerprint as GitHub secrets, and let the OCI CLI on the runner sign requests with it. It works everywhere and every tutorial shows it. It is also a long lived credential sitting in a third party system, which means rotation discipline becomes your problem, and a leaked repository secret becomes a tenancy problem. If you use this pattern, scope the user to a deployer group with minimal policies and rotate the key on a schedule.
Instance principals on self hosted runners
If your runners are compute instances inside your tenancy, they can authenticate as instance principals: the instance itself proves its identity to IAM through a dynamic group, and no key material exists at all. This is the cleanest answer when private cluster endpoints force you onto self hosted runners anyway. The cost is that you now operate the runner fleet, patching included.
Workload identity federation with OIDC
The modern answer for hosted runners. Every GitHub Actions job can mint a short lived OIDC token that states which repository, branch, and workflow produced it. OCI IAM identity domains can be configured to trust those tokens and exchange them for temporary OCI credentials, scoped by policy and expiring in minutes. No stored keys, nothing to rotate, nothing worth stealing from the secrets store. The setup is more involved than pasting an API key, but it is a one time cost, and it is the direction the whole industry has moved.
Our rule of thumb: federation for hosted runners, instance principals for self hosted runners, API keys only as a bridge while you set up one of the other two, with a rotation reminder in the calendar from day one.
The setup walkthrough
This is the order we build it in on client engagements. Each step is independently testable, which matters more than speed.
- Create the OCIR repository and push a test image. Create the repo in the cluster's region, log in with the tenancy namespace and an auth token, push any small image by hand, and confirm it appears. This isolates registry problems from pipeline problems.
- Stand up the pipeline identity. Configure the identity domain trust for GitHub OIDC, or create the deployer user and group. Write two policies: manage repos in the application compartment for pushes, and use clusters in that compartment for kubeconfig generation. Nothing broader.
- Create the Kubernetes deployer RBAC. Bind the pipeline identity to a role in the application namespace that can patch deployments and read rollout status, and nothing cluster wide.
- Store secrets in a GitHub environment. Create a production environment in the repository settings, attach required reviewers, and place the cluster OCID, region, and any remaining credentials there rather than at repository level.
- Write the build and push job. Checkout, build, tag with the commit SHA, push to OCIR. Run it on a branch until it is green. Tags must be immutable;
latestis not a version. - Add the deploy job. Generate the kubeconfig with Oracle's published action, update the deployment image, and wait on rollout status so a failed rollout fails the workflow run.
- Wire approvals and rollback. Gate the deploy job behind the environment reviewers, then rehearse a rollback with
kubectl rollout undobefore you ever need it in anger.
An example workflow
The workflow below is a deliberately plain version of what we deploy: one job builds and pushes, a second deploys behind an environment gate. It uses an API key flavour of auth for legibility; in production we replace the key material with the OIDC exchange described above. Region, namespace, and names are placeholders.
name: build-and-deploy
on:
push:
branches: [ main ]
env:
OCI_CLI_USER: ${{ secrets.OCI_USER_OCID }}
OCI_CLI_TENANCY: ${{ secrets.OCI_TENANCY_OCID }}
OCI_CLI_FINGERPRINT: ${{ secrets.OCI_KEY_FINGERPRINT }}
OCI_CLI_KEY_CONTENT: ${{ secrets.OCI_KEY_CONTENT }}
OCI_CLI_REGION: eu-frankfurt-1
IMAGE_BASE: fra.ocir.io/mytenancy/shop/api
jobs:
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.meta.outputs.image }}
steps:
- uses: actions/checkout@v4
- id: meta
run: echo "image=${IMAGE_BASE}:${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
- name: Log in to OCIR
run: echo "${{ secrets.OCIR_AUTH_TOKEN }}" | docker login fra.ocir.io -u "${{ secrets.OCIR_USERNAME }}" --password-stdin
- name: Build and push
run: |
docker build -t "${{ steps.meta.outputs.image }}" .
docker push "${{ steps.meta.outputs.image }}"
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- name: Configure kubectl for OKE
uses: oracle-actions/configure-kubectl-oke@v1.5.0
with:
cluster: ${{ secrets.OKE_CLUSTER_OCID }}
- name: Roll out new image
run: |
kubectl -n shop set image deployment/api api="${{ needs.build.outputs.image }}"
kubectl -n shop rollout status deployment/api --timeout=180s
Two details earn their keep. The image tag is the commit SHA, so every running pod can be traced to an exact commit, and a redeploy of an old version is just a deploy of an old tag. And the rollout status wait means the workflow run goes red when the pods do, instead of reporting success the moment the manifest was accepted. For chart based applications, the deploy step becomes helm upgrade --install with --atomic and --wait, which buys you automatic rollback on a failed release as well.
Environments and approvals
GitHub environments are the governance layer most teams skip and then wish they had. An environment is a named deployment target with its own secrets, its own protection rules, and its own deployment history. The pattern that works: a staging environment that deploys on every merge to main with no gate, and a production environment that requires one or two named reviewers before the deploy job starts. Secrets scoped to the production environment are only released to jobs that passed the gate, so even a compromised workflow on a branch cannot reach production credentials. Add a branch protection rule so only main can target production, and the audit story writes itself: every production deploy has a commit, an approver, and a timestamp.
Rollback strategy
Decide the rollback story before the first production deploy, because the moment you need it is the worst moment to design it. The baseline is Kubernetes itself: kubectl rollout undo returns a deployment to its previous ReplicaSet in seconds, and the SHA tagged images mean the previous version is always still in OCIR, exactly as built. One level up, keep a manually triggered workflow that deploys an arbitrary tag to an arbitrary environment; when an incident call asks for version N minus one, a person picks the tag and the approver approves, with no laptop kubeconfigs involved. For higher stakes services, run progressive delivery on the cluster, canary or blue green through your mesh or ingress, a topic we cover in our guide to service mesh on OCI. Whichever level you choose, rehearse it quarterly. An untested rollback is a hypothesis.
Secrets: GitHub or OCI Vault
Both stores have a job, and the mistake is asking one to do the other's. GitHub secrets should hold only what the pipeline itself needs to authenticate: the OCIR token if you still use one, the cluster OCID, the federation configuration. They are write only, scoped per environment, and fine for that narrow purpose. Application secrets, database passwords, API keys, certificates, do not belong in GitHub at all, and they especially do not belong in workflow files as plaintext environment variables. They belong in OCI Vault, fetched by the running workload through resource principals at startup. The pipeline ships references, the platform resolves values. With OIDC federation in place, the GitHub side of the secrets ledger can shrink to almost nothing, which is exactly the goal: the fewer values stored, the fewer values leak.
GitHub Actions, OCI DevOps, or Jenkins
Teams rarely choose in a vacuum; they choose against an incumbent. This is how the three options compare for OKE delivery specifically.
| Dimension | GitHub Actions | OCI DevOps | Jenkins |
|---|---|---|---|
| Hosting model | SaaS runners, or self hosted runners you operate | Fully managed inside your OCI tenancy | Servers and agents you run and patch yourself |
| Pricing | Minutes included with plans, then metered; self hosted runners cost compute | No charge for the service itself; you pay for build runtime resources | Free software, real infrastructure and people cost |
| OCI integration | Official Oracle actions plus the OCI CLI; auth is your design decision | Native: resource principals, OCIR, OKE deploy stages built in | Plugins and the OCI CLI, wired by hand |
| Ecosystem | Largest action marketplace, fastest moving | Narrow but sufficient for build, scan, deliver | Vast plugin catalog of very mixed quality and maintenance |
| Approvals and governance | Environments, required reviewers, branch protections | Approval stages in deployment pipelines | Whatever you build or buy on top |
| Best fit | Teams already on GitHub who want pipeline next to code | OCI heavy estates wanting identity native pipelines with no external trust | Estates with deep existing Jenkins investment and dedicated platform staff |
Our usual verdict: if the code is on GitHub, use Actions and spend the saved effort on the auth federation. If your organisation forbids external systems holding any path into the tenancy, the OCI DevOps service removes the trust question entirely because the pipeline runs as a resource principal inside OCI. Keep Jenkins only when the migration cost genuinely exceeds the carrying cost, and price the carrying cost honestly.
Common failures
The same handful of breakages account for most of the support calls. OCIR login failures: a 401 from the registry almost always means the console password was used instead of an auth token, or the tenancy namespace was missing from the username. Kubeconfig token expiry: the kubeconfig that OCI generates uses short lived tokens produced by an exec plugin, so a config exported once and stored as a secret dies quickly; generate it fresh in every run, which is what the Oracle action does. Unreachable API endpoint: hosted runners cannot see a private cluster endpoint, and the fix is self hosted runners in the VCN, not opening the endpoint to the internet. Image pull errors on the cluster: the deploy succeeded but pods sit in ImagePullBackOff because the namespace lacks a registry pull secret, or the node pool architecture does not match the image, an increasingly common miss now that OKE microservice estates mix ARM and x86 node pools. Silent deploy success: no rollout wait, so the pipeline turned green while the pods crash looped. Every one of these is cheap to prevent and expensive to discover in production.
Bringing it together
A GitHub Actions pipeline to OKE is a small amount of YAML wrapped around a small number of decisions, and the decisions matter more than the YAML. Choose short lived credentials over stored keys, scope the pipeline identity to one compartment and one namespace, tag images with commit SHAs, gate production behind an environment with named reviewers, and make the workflow wait for the rollout it claims to have done. Teams that get those five things right run hundreds of deploys a month without drama. If you want a review of an existing pipeline, or help standing up the federation and IAM plumbing properly the first time, that work sits squarely in our DevOps and infrastructure as code practice, and it usually starts with a short assessment of how deploys happen today.
Free white paper
Go deeper on this topic with The OCI Landing Zone and Architecture Guide, a reference architecture for security, networking, and governance on OCI. An independent analyst style report with comparison tables and recommendations, free with a work email. Prefer a monthly summary instead? The OCI Brief delivers one practical OCI briefing a month.
Part of a series
This guide is part of Kubernetes & DevOps on OCI — our complete pillar guide on the topic.
Moving Oracle workloads to OCI, or already running on OCI and not sure the architecture or the spend is right? Most teams bring in a specialist before they commit to a region, a shape, or a Universal Credits number. OCISpecialists.com plans the landing zone, runs the migration, and manages the estate after go live, on a fixed project fee, a managed monthly retainer, or a cost optimization fee paid only on verified savings.