Interview Handbook by Sayed Elmahdy العربية

Topic 22 · Quality & operations

Docker, CI/CD & Git

Dockerfiles, Compose, Kubernetes, GitHub Actions, Git, zero-downtime deployment.

16 questionsJunior · 5Mid · 7Senior · 410 quiz

Concepts

What you will learn: If an application works on your machine but fails after release, make its runtime repeatable, build one artifact, and promote it between environments while observing the change. We cover containers, /, and deployments, with attention to readiness, secrets, and database migrations.

Before you start: An image packages runtime files, a container is a running process created from it, and a pipeline automates build, checks, and release steps. Know basic terminal commands and the difference between application code and environment settings. Docker examples use Bash; review commands before running them on your project.

Select a term with a dotted underline for its meaning, then continue reading from the same place.

#Containers and VMs: what is isolated?

Packaging an application with its runtime and dependencies in a known image reduces environment drift. A container is an isolated process sharing the host kernel; a has its own guest and kernel. Linux containers on Windows normally run inside a Linux VM rather than directly on the Windows kernel.

A VM resembles a detached house with its own utilities; a container resembles an apartment with separate boundaries and shared utilities. This explains overhead, not a security guarantee.

ComparisonContainerVM
UnitIsolated processes and filesystemVirtual machine with an operating system
DensityUsually lighter and quicker to startAdditional guest OS overhead
CompatibilityRequires compatible kernel and architectureMore flexibility in guest OS

A container is not a complete security sandbox, backup system or orchestrator. Do not enable privileged mode merely to work around a permission error; identify the required mount and user.

#Images, layers, volumes and networks

An image is an layered template; a container adds an ephemeral writable layer. A tag is a mutable name, while a digest identifies content. Layers support sharing and build caching, but deleting a secret in a later layer does not remove it from an earlier one.

These Bash commands require Docker and a local store-api:study image whose application listens on port 8080.

docker network create store-backend                 # (1)
docker volume create store-data                     # (2)
docker run -d --name store-api --network store-backend \
  -p 127.0.0.1:8080:8080 store-api:study              # (3)
docker logs store-api
docker inspect store-api
docker stats --no-stream store-api
🧾 Code walkthrough
  1. (1) A dedicated network provides container-name discovery. A database host is its service name, not localhost, which means the current container.
  2. (2) A volume survives independently of a container; creating it does not mount it. The Compose example mounts database storage.
  3. (3) Publish the port on the host's loopback interface. Dockerfile EXPOSE only documents a port.

A bind mount maps a host path; Docker manages a volume. Neither replaces backups and restore drills. Send logs to stdout and stderr rather than files in the writable layer.

#A multi-stage ASP.NET Core 10 Dockerfile

Shipping the adds size and attack surface without helping runtime execution. This example assumes one Store.Api.csproj at the build-context root, without project references or central package files. For a larger solution, copy all referenced project and restore configuration files before restoring. Exclude bin, obj, .git and secret files through .dockerignore.

FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
WORKDIR /src
COPY Store.Api.csproj ./
RUN dotnet restore Store.Api.csproj                         # (1)
COPY . .
RUN dotnet publish Store.Api.csproj -c Release --no-restore \
    -o /out /p:UseAppHost=false                             # (2)

FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra
WORKDIR /app
COPY --from=build /out/ ./
USER $APP_UID                                              # (3)
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Store.Api.dll"]                      # (4)
🧾 Code walkthrough
  1. (1) Restore reruns when dependency inputs change rather than for every source edit.
  2. (2) Release publish produces framework-dependent output; SDK and source stay out of the final stage.
  3. (3) The app user provided by modern images runs without root. Writable directories need appropriate permissions.
  4. (4) Exec-form ENTRYPOINT delivers signals directly to the dotnet process.

Chiseled images omit a shell and package manager; the extra variant includes globalization dependencies such as ICU and tzdata. Do not add a curl or CMD-SHELL without the executable. Use an external probe. For real releases, pin base images by digest and schedule scanning and updates; version tags here aid readability. See chiseled documentation.

#SDK container publishing without a Dockerfile

If an application image only needs published output on a base image, the can build it without a Dockerfile. This Bash example uses the previous project, 10 SDK and a local Docker daemon.

dotnet publish Store.Api.csproj -c Release --os linux --arch x64 \
  /t:PublishContainer \
  -p:ContainerRepository=store-api \
  -p:ContainerImageTag=study \
  -p:ContainerBaseImage=mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra

PublishContainer creates the image; ContainerRepository names it and ContainerImageTag labels it. SDK publishing can also produce archives or publish to a registry through its configuration; not every output requires a daemon. Check ports, user and architecture rather than assuming Dockerfile equivalence.

DockerfileSDK publishing
Controls build stages and RUN stepsLess configuration for packaging a .NET app
Useful for native packages or custom build stepsUseful when the base image is ready and no RUN is needed

Both approaches still need scanning, secrets outside images and artifact identities. See SDK container publishing.

#Compose: readiness is more than process startup

A running database container may not yet accept connections. This local-development example uses the previous Dockerfile and an application configured with the Npgsql provider. Set DB_PASSWORD in the environment first and keep its value out of Git.

services:
  api:
    build: .
    ports: ["127.0.0.1:8080:8080"]
    environment:
      ConnectionStrings__Default: >-
        Host=db;Database=store;Username=store;Password=${DB_PASSWORD:?Set DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy # (1)
  db:
    image: postgres:17
    environment:
      POSTGRES_DB: store
      POSTGRES_USER: store
      POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD}
    volumes: ["pgdata:/var/lib/postgresql/data"] # (2)
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U store -d store"] # (3)
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s
volumes:
  pgdata:
🧾 Code walkthrough
  1. (1) Compose waits for database health when starting the . This does not handle later database outages.
  2. (2) PostgreSQL 17 data resides on a volume; ordinary down preserves it, while down -v deletes it.
  3. (3) pg_isready exists in the PostgreSQL image, not in the chiseled API image. It checks connection acceptance, not applied migrations or every query's permissions.

Run docker compose up --build -d and inspect logs. The API needs a readiness endpoint for required dependencies and bounded retries for transient failures. Environment variables suit this local example; inject production secrets from a secret store. See Compose startup order.

#CI/CD: build once and promote the same artifact

A worked example
  1. Each push builds and checks the project automatically: catches integration breakage early, without alone proving deployment safety.
  2. Promote the same identified artifact from testing to production. Rebuilding for each environment can introduce differences that were not tested.
  3. Continuous delivery keeps a release deployable, possibly with a human release decision; continuous deployment automatically releases eligible changes. Both need environment configuration, recovery planning and database compatibility.

CI integrates small changes with automated builds and tests. Continuous delivery keeps a release ready for approved deployment; continuous deployment automatically deploys every passing change. This GitHub Actions example assumes a root solution, tests configured for dotnet test and a root Dockerfile. Action tags aid readability; pin reviewed full commit SHAs in an operational pipeline.

The jobs assume existing staging and production Container Apps in single-revision mode, configured to pull the image from GHCR; private packages need separate registry credentials. Define AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, RESOURCE_GROUP, APP_NAME and HEALTH_URL in each GitHub environment. Configure environment-specific federation, limited deployment permissions and required production reviewers. This example does not provision infrastructure or run migrations; apply compatible changes through a separate step before rollout.

name: ci-cd
on:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false
jobs:
  verify:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'
      - run: dotnet restore
      - run: dotnet build -c Release --no-restore -warnaserror
      - run: dotnet test -c Release --no-build # (1)
  image:
    needs: verify
    if: github.event_name == 'push'
    runs-on: ubuntu-24.04
    outputs:
      image: ${{ steps.publish.outputs.image }}
    permissions:
      contents: read
      packages: write
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GH_USER: ${{ github.actor }}
      REPOSITORY: ${{ github.repository }}
      REVISION: ${{ github.sha }}
    steps:
      - uses: actions/checkout@v4
      - name: Build and push
        id: publish
        shell: bash
        run: |
          image="ghcr.io/${REPOSITORY,,}:$REVISION" # (2)
          printf '%s' "$GH_TOKEN" | docker login ghcr.io \
            -u "$GH_USER" --password-stdin
          docker build --pull -t "$image" .
          docker push "$image" # (3)
          digest=$(docker inspect --format='{{index .RepoDigests 0}}' "$image")
          echo "image=$digest" >> "$GITHUB_OUTPUT"
  deploy-staging:
    needs: image
    runs-on: ubuntu-24.04
    environment: staging # (4)
    concurrency: deploy-staging
    permissions:
      contents: read
      id-token: write
    env:
      IMAGE: ${{ needs.image.outputs.image }}
      GROUP: ${{ vars.RESOURCE_GROUP }}
      APP: ${{ vars.APP_NAME }}
      HEALTH_URL: ${{ vars.HEALTH_URL }}
    steps:
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
      - run: az extension add --name containerapp --upgrade
      - run: az containerapp update -g "$GROUP" -n "$APP" --image "$IMAGE"
      - run: curl --fail --retry 5 --retry-connrefused "$HEALTH_URL" # (5)
  deploy-production:
    needs: [image, deploy-staging]
    runs-on: ubuntu-24.04
    environment: production # (6)
    concurrency: deploy-production
    permissions:
      contents: read
      id-token: write
    env:
      IMAGE: ${{ needs.image.outputs.image }}
      GROUP: ${{ vars.RESOURCE_GROUP }}
      APP: ${{ vars.APP_NAME }}
      HEALTH_URL: ${{ vars.HEALTH_URL }}
    steps:
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
      - run: az extension add --name containerapp --upgrade
      - run: az containerapp update -g "$GROUP" -n "$APP" --image "$IMAGE"
      - run: curl --fail --retry 5 --retry-connrefused "$HEALTH_URL"
🧾 Code walkthrough
  1. (1) Tests gate image creation. Standard hosted runners provide Docker for tests that need it; monitor integration-test cost and duration.
  2. (2) The registry name is lowercase and the tag identifies the commit. Do not interpolate untrusted inputs directly into scripts.
  3. (3) Only passing main pushes publish images; PR verification has no publication permission. The digest output identifies promoted content.
  4. (4) OIDC supplies a temporary job identity; staging variables select the target resources.
  5. (5) This is a basic readiness smoke check. Extend it to verify the release identity and a useful business scenario.
  6. (6) Production waits for staging and uses the same digest. Configure required reviewers in the GitHub environment; naming an environment alone does not require approval.

Workflow concurrency serializes releases for the same ref so older and newer rollouts cannot overlap. Add image and dependency scanning according to team policy, and update actions and base images regularly. A vulnerability report is only a gate if a policy fails the job. See GitHub Actions security.

#Promotion and deployment strategies

Rebuilding for each environment gives production something different from the tested artifact. Keep configuration and secrets outside it, promote the same image digest, and record version, configuration and migration status.

StrategyBenefitCost or prerequisite
RollingReplace replicas graduallyOld and new coexist; spare capacity is required
Blue-greenSwitch traffic to a prepared environmentTwo environments and shared-data compatibility
CanaryObserve a small traffic share firstRouting, useful metrics and stop criteria
Separate deployment from feature releaseTest both states and retire old flags
  1. Start new instances and pass readiness, not merely process startup.
  2. Shift traffic gradually and observe errors, tail and business outcomes.
  3. During shutdown, remove an instance from routing and allow requests to finish.
  4. If metrics regress, stop rollout or return to a compatible version.

Rolling back an image does not reverse database changes or refund a payment. Define data and external-side-effect recovery before deployment.

#Database migrations without service interruption

During a rolling deployment, old and new code share one . Dropping or directly renaming a column can immediately break old instances. Use expand, migrate and contract instead of one destructive change.

  1. Expand: add a nullable column or a suitable default; assess locks and cost on the real engine.
  2. Deploy code compatible with both schemas. If dual-writing, keep updates consistent and plan for partial failures.
  3. Backfill in small resumable batches while monitoring and locks.
  4. Switch reads after validating data completeness; wait for all old instances and the rollback window to expire.
  5. Contract: remove the old structure in a later release with tested backup and restore procedures.

Run migrations through a single dedicated job with appropriate permissions rather than every replica at startup. An script does not guarantee speed or absence of locks. Test with realistic data volumes and do not assume a Down migration safely restores deleted data.

#Kubernetes: desired state and health probes

Kubernetes reconciles desired state across a cluster; it does not repair business bugs. A Pod is a scheduling unit with containers sharing a network namespace. A Deployment manages replicas and rollouts; a Service provides stable discovery and routes to ready endpoints. Ingress defines routing and needs a controller. ConfigMaps hold configuration; Secrets hold sensitive values, but base64 is not encryption.

This fragment belongs inside a Deployment's Pod template. Replace the image with your release and implement the named endpoints.

containers:
  - name: api
    image: registry.example.com/store-api:release-1
    ports:
      - containerPort: 8080
    resources:
      requests: { cpu: "250m", memory: "256Mi" } # (1)
      limits: { cpu: "1", memory: "512Mi" }
    startupProbe:
      httpGet: { path: /health/live, port: 8080 }
      failureThreshold: 30
      periodSeconds: 2
    readinessProbe:
      httpGet: { path: /health/ready, port: 8080 } # (2)
    livenessProbe:
      httpGet: { path: /health/live, port: 8080 } # (3)
🧾 Code walkthrough
  1. (1) Requests affect scheduling; limits constrain usage. These values are illustrative, not sizing advice.
  2. (2) Failed readiness removes the Pod from ready endpoints; it does not itself restart it.
  3. (3) Persistent liveness failure triggers container restart. A startup probe allows initialization before the other probes become active.

Do not make liveness depend on every external service and create restart storms during database outages. HPA changes replicas from metrics; it does not automatically expand database capacity. Use Kubernetes when its operational capabilities are needed; a managed container platform may be simpler.

#Git: understandable history and reversible decisions

Git records snapshots and relationships between commits. Merge combines histories and may fast-forward without a merge commit. Rebase recreates commits on a new base, changing their identities. Avoid rewriting shared history without explicit coordination.

WorkflowUseful for
Trunk-basedShort branches, frequent integration and
GitHub FlowDeployable main with reviewed PRs
GitFlowScheduled release lines with more branch coordination

These Bash commands assume a practice repository; adapt refs to the situation. Reset moves a branch pointer and may alter the index and working tree depending on mode. Do not use it to undo a published main commit.

git switch -c feature/order-limits
git rebase main                  # (1) replay this private branch
git revert HEAD                  # (2) create a new inverse commit
git bisect start
git bisect bad
git bisect good v1.0.0            # (3) known-good tag must exist
# Test each selected revision; mark it with git bisect good or bad.
git bisect reset
🧾 Code walkthrough
  1. (1) Rebase the private branch; resolve conflicts by understanding and testing the intended result.
  2. (2) Revert adds an inverse commit while retaining history. It may conflict and does not reverse production data.
  3. (3) Bisect narrows commits between known good and bad revisions using a repeatable test; skip untestable revisions.

Conventional commits such as feat(orders): add booking limit and fix(payments): reject duplicate capture express intent and support automation. Useful messages and PRs matter more than prefixes alone. Protect main with review and .

#IIS and Linux with Nginx

Understand the hosting chain to diagnose 502 responses and redirect loops. On Windows, install the appropriate Hosting Bundle after and use an application pool configured for No Managed Code. The ASP.NET Core Module hosts in-process inside w3wp or forwards to Kestrel out-of-process; identity permissions and the published web.config matter.

On Linux, supervise dotnet through systemd or a container orchestrator, with Nginx handling and routing in front of Kestrel. This fragment belongs inside an Nginx server block for local ; public deployment also needs TLS configuration.

location / {
    proxy_pass http://127.0.0.1:5000; # (1)
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme; # (2)
}
🧾 Code walkthrough
  1. (1) Kestrel listens on loopback rather than being directly exposed publicly.
  2. (2) Headers preserve original request information. Run Forwarded Headers before redirection and trust only known proxy addresses.

If Nginx and Kestrel run in separate containers, loopback cannot connect them; use service on a private network. WebSockets need additional upgrade configuration. Persist and securely share Data Protection keys when replicas or deployments must preserve login cookies. See the official Nginx guide.

Interview questions

Check your reasoning

Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?

22.1How is a container different from a VM?Junior
Model answer

I see a container as an isolated process environment that shares the host kernel, while a includes a guest operating system and kernel. Containers usually start faster and offer higher density, but require compatible operating-system and targets. Linux containers on Windows normally use a Linux VM underneath. I choose between them based on , compatibility and operational needs rather than assuming a container provides the same boundary as a virtual machine.

22.2What is the difference between an image and a container?Junior
Model answer

I describe an image as a read-only layered template containing application files and runtime dependencies. A container is an instance of that image with process and a writable layer. Changes in that layer should be treated as disposable. I use volumes or external stores for durable data and identify deployed image content by digest. Tags are convenient labels, but they can move, so a tag alone is weaker evidence of what actually ran.

22.3What makes a good .NET Dockerfile?Mid
Model answer

I use an stage to restore and publish, then copy only published output into an ASP.NET runtime stage. I copy dependency inputs before source to preserve restore caching, run as a non-root user and exclude build output and secrets from the context. I consider chiseled images when their missing shell and package tools fit the application. I pin release inputs, scan and update images, and verify globalization, filesystem permissions and shutdown behavior.

22.4When would you use PublishContainer instead of a Dockerfile?Mid
Model answer

I use container publishing when the image mainly consists of publish output on a suitable base image. It reduces Dockerfile maintenance and supports configuring the repository, tag, base image and target platform. I use a Dockerfile when I need custom build stages or operating-system changes that require RUN instructions. Both approaches still need controlled base-image updates, external secrets and deployment verification; changing the packaging tool does not remove those responsibilities.

22.5Does EXPOSE publish a port?Junior
Model answer

I treat EXPOSE as image metadata documenting the intended listening port. It does not publish that port on the host or make an application listen there. With Docker I publish through the run port mapping or Compose ports setting, and ensure the application binds to the correct interface inside the container. I also choose the host binding deliberately: a loopback binding is useful behind a local , while a public binding exposes a larger surface.

22.6How do you keep data when containers are replaced?Mid
Model answer

I put durable data on managed storage, a database service or an explicitly mounted volume rather than a container's writable layer. A bind mount maps a host directory, while a named volume is managed by Docker. I document the storage lifecycle and avoid destructive cleanup commands during routine deployment. Persistence alone is not a backup strategy, so I also define retention, where needed, and regular restore tests against the required recovery objectives.

22.7Why is depends_on alone insufficient for database startup?Mid
Model answer

I distinguish a started container from a ready dependency. Basic depends_on orders startup, while a service_healthy condition waits for the dependency's . Even that only proves what the check measures; pg_isready does not prove migrations are applied. I retain bounded connection retries and application readiness checks because the database may become unavailable after startup. I also ensure the health-check executable exists rather than assuming a minimal application image includes curl or a shell.

22.8What is CI/CD?Junior
Model answer

I use continuous integration to build and test small changes frequently so integration problems surface early. Continuous delivery keeps passing artifacts ready for deployment, often with a production approval. Continuous deployment automatically deploys changes that pass the release gates. My pipeline produces a versioned artifact, verifies it in staging and promotes the same content to production. I separate environment configuration from the artifact and define rollback and observability before enabling automatic releases.

22.9What does build once, deploy many mean?Senior
Model answer

I build one artifact from a reviewed revision, record its content identity and promote that same artifact through environments. I inject configuration and secrets at deployment instead of recompiling for staging and production. That reduces the chance of releasing untested content. It does not mean the environments are identical: I still verify configuration, permissions, database compatibility and infrastructure. I record those inputs alongside the artifact digest so a deployment can be audited and reproduced.

22.10How do rolling, blue-green and canary deployments differ?Senior
Model answer

I use rolling updates to replace replicas gradually while maintaining capacity. Blue-green prepares a second environment and switches traffic after validation, at the cost of extra capacity and shared-data considerations. Canary deployment exposes a small traffic share first and expands only when metrics remain acceptable. All three need readiness, compatible schemas and a recovery plan. I choose based on traffic control, risk and operational capability rather than assuming any strategy guarantees zero downtime by itself.

22.11How do you deploy a breaking database change safely?Senior
Model answer

I use expand and contract so old and new application versions can coexist. I first add compatible , deploy code that tolerates both representations, backfill in resumable batches and switch reads after verification. I remove the old structure only after old instances and the rollback window are gone. I run migrations through one controlled job and test lock behavior on realistic data. Reverting the application image cannot restore deleted data, so recovery needs separate planning.

22.12What are the main Kubernetes objects a developer should know?Mid
Model answer

I use Pods as scheduling units, Deployments to maintain replicas and roll out changes, and Services for stable discovery and routing to ready endpoints. Ingress describes external routing but needs a controller. ConfigMaps provide configuration and Secrets hold sensitive values with access controls. I also understand resource requests, limits and probes because they directly affect scheduling and availability. Kubernetes automates reconciliation, but application correctness and secure configuration remain the team's responsibility.

22.13How do readiness and liveness probes differ?Mid
Model answer

I use readiness to decide whether an instance should receive traffic and liveness to decide whether its container needs restarting. A startup probe allows slow initialization before the other probes become active. I avoid making liveness depend on a shared database because a database outage could restart every application instance without fixing anything. Readiness can reflect dependencies needed to serve requests, but its design should also consider whether removing all replicas would make an outage worse.

22.14What is the difference between merge and rebase?Mid
Model answer

I use merge to combine branch histories; it may create a merge commit or simply fast-forward when possible. Rebase replays commits on a different base and gives them new identities, producing a more linear history. I rebase private work when that helps review, but avoid rewriting history that colleagues already depend on. For either operation I resolve conflicts by understanding the intended behavior and running checks rather than blindly choosing one side.

22.15When should you use revert rather than reset?Junior
Model answer

I use revert to undo a published change because it adds a new commit with the inverse change and preserves shared history. Reset moves a branch pointer and can also change the index or working tree depending on its mode, so it is mainly a local history tool. Neither operation reverses production side effects or database changes. I also check whether later commits depend on the reverted behavior before treating the revert as a complete fix.

22.16How would you diagnose a deployment returning 502?Senior
Model answer

I the request from the proxy to the application. I check proxy errors, process or service status, startup logs, the configured upstream port and whether Kestrel is listening on the expected interface. On I also inspect Hosting Bundle and application identity permissions. I test local reachability before changing public routing. If startup succeeds but readiness fails, I inspect required dependencies and configuration rather than repeatedly restarting an instance without preserving evidence.

Test yourself

Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.

Score: 0 / 0

What does a multi-stage Dockerfile keep out of the final image?

Only publish output is copied into the runtime stage.

Why does a curl healthcheck fail in an unmodified chiseled ASP.NET image?

Use an available executable or an external probe.

Which identity best selects exactly the image tested in staging?

A digest identifies content; tags may move.

A failed readiness probe normally causes what?

Readiness controls routing; persistent liveness failure controls restart.

First step before renaming a database column during rolling deployment?

Expansion lets old and new versions coexist.

A bad commit is already shared on main. Which Git operation preserves history?

Revert adds an inverse commit.

Which statement about Compose service_healthy is correct?

The guarantee is limited to the check and startup ordering.

Which Git tool narrows down a regression between good and bad revisions?

Bisect selects revisions to narrow the introducing change.

What does a feature flag separate?

Code can be deployed while the feature remains disabled.

What should a proxy deployment do with forwarded headers?

Headers from untrusted sources can be forged.