In 05-02 we deployed orders-service to Kubernetes by typing kubectl apply by hand. That is exactly what TechCorp cannot afford: with six services and a gateway, each deploying several times a week, the human hand is slow, inconsistent and the source of the "Thursday-night deployments with rollbacks" of Module 1. Continuous integration (CI) makes every change get tested automatically in minutes; continuous deployment (CD) takes whatever passes the tests all the way to production without intervention. This lesson builds the orders-service pipeline with GitHub Actions: unit tests, integration tests with Testcontainers and contract tests with Pact (04-05), building and publishing the image of 05-01, can-i-deploy as a gate, updating the Kustomize manifests of 05-02 and deploying with GitOps. At the end we apply Luis's rule so the other five services do not copy 150 lines of YAML, and we measure the outcome with the DORA metrics. How to change version without interrupting the service belongs to 05-04.

Contents

  1. What CI/CD changes with microservices
  2. The stages of TechCorp's pipeline
  3. .github/workflows/ci.yml of orders-service, line by line
  4. Contract tests in the pipeline: Pact Broker and can-i-deploy
  5. Continuous deployment: .github/workflows/cd.yml
  6. GitOps with Argo CD: techcorp/platform as the source of truth
  7. Versions, tags and changelogs per service
  8. Environments and promotion: dev → staging → prod
  9. Secrets in CI
  10. The @techcorp/common-http pipeline
  11. Luis's rule: one reusable workflow for the six services
  12. DORA metrics: measuring the pipeline

  1. What CI/CD changes with microservices

The techcorp-shop monolith had one pipeline: a 40-minute suite, one artifact, one coordinated deployment. With microservices:

Aspect Monolith Microservices (TechCorp)
Number of pipelines One big one One per repository: six services + gateway + library + platform
Duration 40 min (everything is always tested) 5-8 min per service (only what changes is tested)
Artifact One package One image per service, with its version
Deployment All or nothing, coordinated Independent: Orders deploys without waiting for Catalog
Versioning One global version Semver per service; compatibility is guaranteed by contracts (03-06) and Pact (04-05)
Risk per deployment High (a lot of change at once) Low (little change, easy to revert)
Hidden cost Few Many pipelines to maintain: they must be standardized (section 11)

Deployment independence is the central promise of the architecture (01-02, 02-01) and it only holds if each service's pipeline can deploy on its own, without a "release train" that groups everybody. The precondition for daring to do so is the confidence that contract tests provide: it is Pact, not a whole-system E2E test, that answers "will I break someone?".

  1. The stages of TechCorp's pipeline

flowchart LR
    A[lint] --> B[unit + component<br/>npm test] --> C[integration<br/>Testcontainers] --> D[contract<br/>Pact + publish pacts]
    D --> E[build image<br/>ghcr.io] --> F[scan<br/>Trivy] --> G[can-i-deploy<br/>staging]
    G --> H[deploy staging<br/>Job + rollout] --> I[minimal E2E<br/>gateway 8080] --> J[can-i-deploy prod] --> K[promotion to prod<br/>GitOps]
    style E fill:#dfe,stroke:#393
    style G fill:#ffd,stroke:#a90
    style J fill:#ffd,stroke:#a90
Stage What it does When Fails if...
Lint eslint with the template's configuration Every push and PR Style or static errors
Unit + component npm test (Jest, Supertest, no Docker; 04-05) Every push and PR Domain logic or routes
Integration npm run test:integration with Testcontainers (PostgreSQL 16, RabbitMQ) Every push and PR Outbox, saga consumer, SQL
Contract npm run test:contract generates pacts (consumer) or verifies (provider); publication to the Broker Every push and PR A broken contract
Build + push The image of 05-01 with sha-<commit> and semver tags Only on main and on tags Dockerfile or npm ci
Scan Trivy over the image After the build Critical CVE (policy in 07-04)
can-i-deploy Is it compatible with what is in the target environment? Before every deployment Some pact not verified
Deploy to staging Migrations Job + kubectl apply -k + rollout status Every push to main Migration or startup
Minimal E2E The single E2E of 04-05 against staging After deploying to staging Wiring
Promotion to prod Image change in the prod overlay (GitOps) Manual or automatic depending on the service —

Everything before the image is CI: fast, on every change. Everything after is CD.

  1. .github/workflows/ci.yml of orders-service, line by line

# techcorp/orders-service/.github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
    tags: ["v*"]                    # v1.0.1 → image 1.0.1 (section 7)
  pull_request:

permissions:
  contents: read
  packages: write                   # required to push to ghcr.io with GITHUB_TOKEN

env:
  IMAGE: ghcr.io/techcorp/orders-service
  PACT_BROKER_BASE_URL: https://pact.techcorp.example

jobs:
  tests:
    runs-on: ubuntu-latest          # comes with Docker installed: Testcontainers works with no extra setup
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm                # caches ~/.npm between runs based on package-lock.json
          registry-url: https://npm.pkg.github.com
          scope: "@techcorp"        # @techcorp/common-http resolves against GitHub Packages (04-01)

      - run: npm ci
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}   # read access to the organization's packages

      - run: npm run lint
      - run: npm test                                    # unit + component + events: seconds, no Docker
      - run: npm run test:integration                    # Testcontainers starts postgres:16 and rabbitmq on the runner
      - run: npm run test:contract                       # generates pacts/orders-service-catalog-service.json

      - name: Publish pacts to the Broker
        run: |
          npx pact-broker publish pacts \
            --consumer-app-version ${{ github.sha }} \
            --branch ${{ github.head_ref || github.ref_name }} \
            --broker-token ${{ secrets.PACT_BROKER_TOKEN }}

  image:
    needs: tests
    if: github.event_name == 'push'                       # no image is published on a PR
    runs-on: ubuntu-latest
    outputs:
      tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3               # BuildKit: required for --mount=type=secret and remote cache
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: meta
        uses: docker/metadata-action@v5                   # computes the tags according to the event
        with:
          images: ${{ env.IMAGE }}
          tags: |
            type=sha,prefix=sha-,format=short             # always: sha-9f3c2ab
            type=semver,pattern={{version}}               # only on tags v1.0.1 → 1.0.1
      - name: Prepare .npmrc for the build (secret, not a layer)
        run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > /tmp/npmrc
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          secrets: npmrc=/tmp/npmrc                       # the --secret id=npmrc of the 05-01 Dockerfile
          cache-from: type=gha                            # layer cache between runs (the npm ci of 05-01 §4)
          cache-to: type=gha,mode=max
      - name: Vulnerability scan (details in 07-04)
        uses: aquasecurity/[email protected]
        with:
          image-ref: ${{ env.IMAGE }}:sha-${{ github.sha }}
          severity: CRITICAL
          exit-code: "1"

Reading notes:

  • Two jobs (tests, image) with needs: the image is only built if everything passes; on a PR (pull_request) only tests runs.
  • ubuntu-latest comes with Docker, so test:integration works as is: Testcontainers pulls postgres:16 and rabbitmq:3-management on the runner. That is the reason we separated test (no Docker) from test:integration in 04-05.
  • docker/metadata-action produces the two tags of 05-01 without hand-written scripts: sha-<commit> always, and the semver when the event is a v1.0.1 tag.
  • secrets: npmrc= in build-push-action feeds the --mount=type=secret,id=npmrc of the Dockerfile: the token never ends up in any layer.
  • cache-from/to: type=gha stores the layers in the GitHub Actions cache: a change in src/ does not repeat npm ci in CI, just as on the laptop.
  • Minimal permissions: contents: read to clone, packages: write to publish. No personal tokens.

  1. Contract tests in the pipeline: Pact Broker and can-i-deploy

In 04-05 the pact file traveled by hand from orders-service to catalog-service. In CI it travels through the Pact Broker (self-hosted or PactFlow), which stores each pact with the consumer's version (github.sha) and branch, and each verification result with the provider's version:

sequenceDiagram
    participant P as CI orders-service (consumer)
    participant B as Pact Broker
    participant C as CI catalog-service (provider)
    P->>B: publish pacts (version sha-9f3c2ab, branch main)
    B-->>C: webhook: there is a new pact to verify
    C->>C: npm run test:contract (Verifier with stateHandlers, 04-05)
    C->>B: verification result (catalog sha-1a2b3c: OK)
    P->>B: can-i-deploy --pacticipant orders-service --version sha-9f3c2ab --to-environment staging
    B-->>P: yes: the catalog deployed in staging verified this pact

In the provider's CI (catalog-service), the test:contract of 04-05 runs with pactBrokerUrl instead of pactUrls, with publishVerificationResult: true and providerVersion: process.env.GITHUB_SHA, and after deploying it records which environment each version is in (pact-broker record-deployment --environment staging). With that, the gate before deploying is one line:

  staging-gate:
    needs: image
    runs-on: ubuntu-latest
    steps:
      - run: |
          npx pact-broker can-i-deploy \
            --pacticipant orders-service --version ${{ github.sha }} \
            --to-environment staging \
            --broker-base-url ${{ env.PACT_BROKER_BASE_URL }} --broker-token ${{ secrets.PACT_BROKER_TOKEN }}

can-i-deploy answers "yes" only if all the provider versions currently deployed in staging have successfully verified the pacts of this version of Orders (and, if Orders were a provider to someone, if its deployed consumers are still satisfied). It is the integration guarantee at the cost of one HTTP query: the reason TechCorp's single E2E test can remain a single one.

  1. Continuous deployment: .github/workflows/cd.yml

The deployment to staging is done directly from the workflow: it updates the image tag in the Kustomize overlay (05-02), launches the migrations Job, applies and waits for the rollout, and runs the E2E test.

# techcorp/orders-service/.github/workflows/cd.yml
name: CD staging
on:
  workflow_run:
    workflows: [CI]
    branches: [main]
    types: [completed]

permissions: { contents: read }

jobs:
  staging:
    if: github.event.workflow_run.conclusion == 'success'
    runs-on: ubuntu-latest
    environment: staging                              # GitHub environment: its own secrets and protection rules
    env:
      TAG: sha-${{ github.event.workflow_run.head_sha }}
    steps:
      - uses: actions/checkout@v4
        with:
          repository: techcorp/platform               # the manifests live in the platform repo (05-02)
          token: ${{ secrets.PLATFORM_TOKEN }}
      - uses: azure/setup-kubectl@v4
      - name: Staging cluster credentials
        run: echo "${{ secrets.KUBECONFIG_STAGING }}" | base64 -d > $HOME/.kube/config
      - name: Pin the image in the staging overlay
        working-directory: k8s/orders-service/overlays/staging
        run: kustomize edit set image ghcr.io/techcorp/orders-service=ghcr.io/techcorp/orders-service:${{ env.TAG }}
      - name: Migrations (05-02 Job): delete the previous one, apply, wait
        run: |
          kubectl -n techcorp delete job orders-service-migrations --ignore-not-found
          kubectl kustomize k8s/orders-service/overlays/staging | kubectl apply -f -
          kubectl -n techcorp wait --for=condition=complete job/orders-service-migrations --timeout=180s
      - name: Wait for the rollout
        run: kubectl -n techcorp rollout status deploy/orders-service --timeout=180s
      - name: Minimal E2E against staging (04-05)
        run: GATEWAY_URL=https://api-staging.techcorp.example npm run test:e2e
      - name: Record the deployment in the Pact Broker
        run: npx pact-broker record-deployment --pacticipant orders-service --version ${{ github.event.workflow_run.head_sha }} --environment staging --broker-token ${{ secrets.PACT_BROKER_TOKEN }}

Two details compared with 05-02: the migrations Job is now called plainly orders-service-migrations and the workflow deletes the previous one before applying (a Job is immutable); it is the alternative to the versioned name, and it works because migrate.js is idempotent. And kustomize edit set image modifies the images: line we left ready in the overlay: the YAML remains the source of truth, the pipeline only changes a tag. With Helm the equivalent would be helm upgrade --set image.tag=$TAG.

rollout status is what turns "I applied it" into "it is deployed and ready": it waits for the new replicas to pass the readinessProbe and fails (and the job fails) if they do not manage it within 180 s. What Kubernetes does meanwhile with the old replicas is 05-04.

  1. GitOps with Argo CD: techcorp/platform as the source of truth

For production, TechCorp does not want a workflow holding a kubeconfig in a secret to run kubectl apply against the production cluster. It adopts GitOps: the desired state of production is what is in git (techcorp/platform, main branch, overlays/prod), and an agent inside the cluster (Argo CD; Flux is equivalent) reads it and applies it continuously.

flowchart LR
    CI[CI orders-service] -->|image sha-9f3c2ab| REG[(ghcr.io)]
    CI -->|PR: overlays/prod images newTag=1.0.1| GIT[(techcorp/platform)]
    REV[Review + merge<br/>Luis or the pipeline] --> GIT
    ARGO[Argo CD<br/>in the prod cluster] -->|pull every 3 min / webhook| GIT
    ARGO -->|kubectl apply| K8S[Production cluster]
    ARGO -.->|detects drift| K8S
# techcorp/platform/argocd/orders-service-prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: orders-service-prod, namespace: argocd }
spec:
  project: techcorp
  source:
    repoURL: https://github.com/techcorp/platform
    targetRevision: main
    path: k8s/orders-service/overlays/prod
  destination: { server: https://kubernetes.default.svc, namespace: techcorp }
  syncPolicy:
    automated: { prune: true, selfHeal: true }   # applies what is in git; reverts manual changes in the cluster

Why TechCorp adopts it: (1) audit: every production deployment is a reviewable commit in platform; (2) no cluster credentials outside the cluster: Argo CD pulls, nobody pushes; (3) drift detection: if someone runs kubectl scale by hand on a Friday, Argo brings it back to the git state (or warns); (4) rollback = revert the commit. The migrations Job is annotated with argocd.argoproj.io/hook: PreSync and hook-delete-policy: BeforeHookCreation, which is the GitOps way of "delete the previous one and run before the Deployment". Argo CD and Flux can also deploy to staging; TechCorp starts with the direct workflow in staging to learn and with GitOps in production, and will unify later on.

  1. Versions, tags and changelogs per service

  • Semver per service: orders-service 1.0.1 has nothing to do with catalog-service 1.4.2. Major = incompatible contract change (which in 03-06 means /v2/), minor = compatible functionality, patch = fix.
  • The sha-<commit> image is always built; the semver, when tagging: git tag v1.0.1 && git push --tags triggers ci.yml with the tag and metadata-action adds 1.0.1 to the same image (same digest) that already went through staging as sha-.... Nothing is rebuilt for production.
  • Changelog: CHANGELOG.md in every repository, fed by hand or generated from commit messages following the conventional commits convention (feat:, fix:, feat!:), which tools such as semantic-release or release-please use to compute the version and publish the release automatically. TechCorp adopts the message convention; automating the version is left as the next step.

  1. Environments and promotion: dev → staging → prod

Environment Cluster Who deploys Which image Data
dev kind on the laptop or Compose (05-01) The developer :local or sha-... Seed
staging Shared cluster, small-sized managed services cd.yml on every push to main sha-<commit> Synthetic, anonymized
prod Production cluster, Argo CD Merge of the promotion PR (human review for Orders and Payments; automatic for Catalog and Notifications) 1.0.1 (same image) Real

The three environments apply the same Kustomize base with different overlays (05-02): the only things that change between staging and prod are the image tag, the replicas and two ConfigMap keys. Promoting means opening a PR in platform that changes newTag in overlays/prod (the workflow opens it on its own with peter-evans/create-pull-request); reviewing and merging is the approval.

  1. Secrets in CI

  • secrets.GITHUB_TOKEN: generated by GitHub for each run, with the declared permissions; it works for ghcr.io and GitHub Packages without creating anything.
  • Repository/organization secrets (PACT_BROKER_TOKEN, PLATFORM_TOKEN) and environment secrets (KUBECONFIG_STAGING, tied to environment: staging, with required reviewers if desired).
  • OIDC instead of long-lived keys: to talk to the cloud (AWS/GCP/Azure) or to an external registry, the workflow obtains an ephemeral token through federated identity (permissions: id-token: write + aws-actions/configure-aws-credentials) instead of storing an access key in a secret. It is the recommended practice and the one TechCorp will use for the production cluster if one day a workflow needs to touch it (with GitOps, it does not).
  • Never echo a secret into the logs; GitHub masks them, but base64 or transformations expose them.

  1. The @techcorp/common-http pipeline

The library (04-01) has its own, shorter workflow: npm ci, npm test, and on a v* tag, npm publish to GitHub Packages:

# techcorp/common-http/.github/workflows/publish.yml (excerpt)
on: { push: { tags: ["v*"] } }
permissions: { contents: read, packages: write }
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm, registry-url: https://npm.pkg.github.com, scope: "@techcorp" }
      - run: npm ci && npm test
      - run: npm publish
        env: { NODE_AUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }

Each service pins in its package.json the version it uses ("@techcorp/common-http": "^2.3.0") and updates it whenever it wants, with Renovate/Dependabot opening the PR: the library does not force anyone to redeploy, which was the condition for it to exist (04-01).

  1. Luis's rule: one reusable workflow for the six services

"If it is done more than once per service, automate it before the second time." The ci.yml of section 3 has 80 lines and would be identical in Catalog, Inventory, Payments, Notifications and Customers except for the name. GitHub Actions allows a reusable workflow (workflow_call) that lives in techcorp/platform:

# techcorp/platform/.github/workflows/node-service-ci.yml
on:
  workflow_call:
    inputs:
      service-name: { required: true, type: string }
      with-integration: { type: boolean, default: true }    # Notifications has no DB: it can disable it
    secrets:
      PACT_BROKER_TOKEN: { required: true }
jobs:
  tests: ...           # exactly the steps of section 3, with ${{ inputs.service-name }} where orders-service was
  image: ...

And in each service, ci.yml shrinks to:

# techcorp/catalog-service/.github/workflows/ci.yml
name: CI
on: { push: { branches: [main], tags: ["v*"] }, pull_request: }
permissions: { contents: read, packages: write }
jobs:
  ci:
    uses: techcorp/platform/.github/workflows/node-service-ci.yml@v1     # @v1: tag of the platform repo; updated on purpose
    with: { service-name: catalog-service }
    secrets: inherit

The reusable workflow is to the pipelines what @techcorp/common-http is to the services: it is versioned (@v1), each service adopts the new version when it wants, and the node-service-template template (04-01) already ships these nine lines. A change to the pipeline (adding Trivy, bumping an action's version) is made once.

  1. DORA metrics: measuring the pipeline

The four DORA metrics (DevOps Research and Assessment) measure whether all of the above is good for anything:

Metric What it measures TechCorp before (monolith, 01-05) Target with the pipeline
Deployment frequency How often production is reached Thursday night, biweekly Several times a day, per service
Change lead time From commit to production 1-2 weeks (waiting for the train) < 1 day (< 1 hour for hotfixes)
Change failure rate % of deployments that cause an incident or a rollback High: frequent rollbacks < 15%
Time to restore (MTTR) How long it takes to recover the service after a failure 50 min without selling on Black Friday Minutes: rollout undo or revert the commit (05-04)

The first two are provided by the pipeline itself (commit date, date of the Argo sync); the other two need the incident management of 06-05. What matters is the relationship between them: deploying more often with smaller changes lowers the failure rate and the MTTR, which is the opposite of the Thursday-night intuition ("we deploy rarely to risk little").

Common Mistakes and Tips

  • A "system" pipeline that tests and deploys all the services together. It is the monolith's release train with more YAML. One pipeline per service and contracts as the guarantee.
  • Rebuilding the image for production. What was tested is not what gets deployed. Semver tag on the already-tested sha- image.
  • can-i-deploy as an optional step or continue-on-error. It ends up being ignored. It is a gate: if it says no, there is no deployment.
  • kubectl apply without rollout status. The pipeline is green and the new replicas are in CrashLoopBackOff.
  • Copying ci.yml between repositories. By the third version of an action, six identical PRs. workflow_call.
  • Long-lived secrets in variables when OIDC is available; a developer's personal tokens in organization secrets (they stop working when they leave).
  • Tip: run the workflow locally with act to debug; and keep in each repository's README the link to the reusable workflow and to the runbooks, so that "how is this deployed" has an answer.

Exercises

Exercise 1. The CI of catalog-service (provider) must verify the pacts its consumers publish. Write the steps of the contract job of its ci.yml (without repeating checkout/setup-node) and explain which environment variables the Verifier of 04-05 needs to work with the Broker instead of a local file, and why it must run record-deployment after deploying.

Exercise 2. A Payments developer opens a PR that changes the payload of the payment.confirmed event. At which stage of the Payments pipeline would an incompatibility with orders-service be detected, with the tools of 04-05, and what would need to be added so that can-i-deploy blocked it?

Exercise 3. Marta asks that Catalog and Notifications be promoted to production automatically after passing the E2E test in staging, but that Orders and Payments require human approval. Describe how this is implemented with what we have seen (GitHub environments, workflow_call, promotion PR, Argo CD) without duplicating the workflow.

Solutions

Solution 1. Steps: npm ci → npm run test:contract with PACT_BROKER_BASE_URL, PACT_BROKER_TOKEN, GIT_COMMIT=${{ github.sha }} and GIT_BRANCH=${{ github.ref_name }} in env; the Verifier of 04-05 uses pactBrokerUrl and pactBrokerToken instead of pactUrls, providerVersion: process.env.GIT_COMMIT, providerVersionBranch, publishVerificationResult: true (only in CI: process.env.CI === 'true') and consumerVersionSelectors: [{ mainBranch: true }, { deployedOrReleased: true }] to verify both the pacts of each consumer's main branch and those of the deployed versions. After deploying to an environment, pact-broker record-deployment --pacticipant catalog-service --version $GIT_COMMIT --environment staging tells the Broker which version of Catalog is in staging: without that record, the Orders can-i-deploy does not know which provider to check against and answers "no" for lack of information.

Solution 2. At the contract stage, but on the consumer's side: 04-05 fixed JSON Schemas for events with Ajv and a test that orders-service tolerates new fields in payment.confirmed; if Payments removes or renames a field (amount → quantity), it is caught by Payments' own schema test if the schema is shared in contracts/asyncapi.yaml (it fails in Payments' npm test), or, if not, by the Orders events test when it updates the schema. For can-i-deploy to block it, a message pact would be needed (Pact supports message pacts: Orders declares the message it expects; Payments verifies it with a MessageProviderPact that produces the real event) published to the same Broker: then the Payments→Orders relationship appears like any other and can-i-deploy checks it. It is the natural evolution of the JSON Schema of 04-05.

Solution 3. The reusable CD workflow (node-service-cd.yml) receives an auto-promotion: boolean input. Its last job opens the promotion PR in techcorp/platform (change of newTag in overlays/prod); if auto-promotion is true, the same job merges it (gh pr merge --auto), and Argo CD syncs; if it is false, the PR stays open and the CODEOWNERS of k8s/orders-service/ and k8s/payments-service/ requires the review of Luis or the Payments lead. In addition, the production job uses environment: production in GitHub with required reviewers for those two services. No workflow is duplicated: Catalog calls with auto-promotion: true, Orders with false.

Conclusion

TechCorp's pipeline turns every push into an automatic, per-service sequence: ci.yml with actions/setup-node@v4 (Node 20, npm cache, GitHub Packages), npm run lint, npm test, npm run test:integration with Testcontainers on the runner, npm run test:contract and publication of pacts to the Pact Broker, docker/build-push-action with sha-<commit> and semver tags towards ghcr.io/techcorp/orders-service (with permissions: packages: write and the .npmrc as a build secret), scanning with Trivy, and can-i-deploy as a gate; cd.yml that pins the image in the Kustomize overlay (kustomize edit set image), runs the migrations Job, does kubectl apply + rollout status and launches the E2E test against staging; GitOps with Argo CD and techcorp/platform as the source of truth for production; semver and sha- on the same image; environments with the same manifests; GitHub secrets and OIDC; the @techcorp/common-http pipeline; the reusable workflow node-service-ci.yml@v1 that applies Luis's rule; and the DORA metrics to check that Thursday nights are over. One question remains that rollout status hides: what exactly happens to the traffic while the 1.0.0 replicas give way to the 1.0.1 ones, and how is a version deployed to 10% of users before giving it to everyone? That is the next lesson: deployment strategies.

Microservices Course

Module 1: Introduction to Microservices

Module 2: Microservice Design

Module 3: Communication between Microservices

Module 4: Implementing Microservices

Module 5: Deployment and Orchestration

Module 6: Monitoring and Maintenance

Module 7: Security in Microservices

Module 8: Case Studies and Practical Examples

© Copyright 2026. All rights reserved