A .NET team has automated its build and deployment. Every change pushed to the main branch is compiled, tested, and sent toward production. The pipeline is fast, the build is usually green, and releases no longer require a developer to publish from a local machine.
Then an unfinished booking feature appears in production. The code compiled correctly, the existing tests passed, and the deployment itself succeeded. The failure was not caused by missing automation. It was caused by automation without enough control.
A safe delivery process must answer more than one question. It must verify whether the code builds, whether the behavior is correct, whether security checks pass, whether the feature is ready for users, whether the right people approved the release, and whether the application remains healthy after deployment.
The Real Problem Is Not Deployment Speed
Continuous Integration, Continuous Delivery, Continuous Security, and Continuous Feedback solve different parts of the delivery problem.
| Practice | Main question |
|---|---|
| Continuous Integration | Can this change be merged safely? |
| Continuous Delivery | Can this validated version move through environments safely? |
| Continuous Security | Does the change introduce security risks anywhere in the lifecycle? |
| Continuous Feedback | What is the deployed system doing in real use? |
Treating these practices as one automatic "build and deploy" button creates dangerous gaps.
A successful compilation does not prove that a business workflow works. A passing unit test does not prove that a feature is ready to expose. A deployment that completes without errors does not prove that production is healthy. A pull request approval does not replace runtime access control.
The delivery process must connect people, processes, tools, and operational evidence around one goal: delivering useful software without sacrificing quality or security.
The Scenario
Assume a team is developing a .NET travel booking application. A new feature allows customers to reserve an optional airport transfer while booking a trip.
The feature touches several parts of the system:
- An ASP.NET Core endpoint
- Pricing logic
- A user interface
- Automated tests
- Deployment configuration
- Runtime monitoring
The team wants developers to merge partial work frequently so branches do not drift apart. However, customers must not see the transfer option until the complete workflow has passed functional testing and business approval.
The delivery path needs the following components:
Developer branch
|
v
Pull request
|
+--> Build and automated tests
|
+--> Static quality and security checks
|
+--> Peer review
|
v
Protected main branch
|
v
Development environment
|
+--> Integration and functional tests
|
v
Staging environment
|
+--> Approval and business validation
|
v
Production environment
|
+--> Feature flag controls exposure
|
+--> Monitoring provides feedback
Each transition is a gate. A later stage should not compensate for a missing earlier stage.
Step 1: Protect the Main Branch
The main branch should represent code that has passed the team's agreed integration checks. Developers should not push directly to it.
Require changes to arrive through pull requests. A pull request creates a review point where the team can inspect the implementation, discuss risks, and run automated validation before merge.
The pull request should not be approved only because the code looks reasonable. The reviewer should confirm that the change includes the controls required by the delivery process.
For the airport transfer feature, the review should verify:
- The new business behavior has automated tests
- Existing booking behavior still passes
- The unfinished user-facing capability is protected by a feature flag
- No secret or environment-specific credential is committed
- Static analysis does not report unacceptable quality or security findings
- The change can be disabled without removing the deployment
A protected branch and required pull request checks prevent accidental or unreviewed code from entering the release path.
They do not determine whether a feature should be visible in production. That requires a separate runtime control.
Step 2: Make CI Validate Every Change
Continuous Integration, or CI, means integrating small changes frequently and validating them automatically. The fewer differences that exist between a developer branch and the main branch, the easier it is to detect problems before they accumulate.
A basic .NET validation sequence can restore dependencies, compile the application, run tests, and create a deployable output.
dotnet restore
dotnet build --configuration Release --no-restore
dotnet test --configuration Release --no-build
dotnet publish --configuration Release --no-build --output ./publish
These commands are only the mechanical foundation. A useful CI gate must decide which failures block the merge.
For example:
- Compilation failure blocks the merge
- Unit test failure blocks the merge
- Functional test failure blocks promotion
- A serious static analysis finding blocks the merge
- A detected secret blocks the merge
- A vulnerable dependency requires review or blocks the merge according to team policy
The exact policy depends on the application, but the policy must be explicit. When a pipeline reports problems but still allows the change to merge, the checks become informational rather than protective.
Step 3: Add Security to the Normal Workflow
Security should not be a separate activity performed shortly before a major release. By then, insecure design choices may already be spread across several components.
Continuous Security places security checks throughout development and delivery.
For pull requests and CI, that can include checks for:
- Insecure code patterns
- Vulnerable dependencies
- Accidentally committed secrets
- Weak or unsafe configuration
- Missing authorization around sensitive operations
- Static analysis findings that may indicate exploitable behavior
Security automation does not remove human responsibility. It gives reviewers earlier evidence and makes the same minimum controls run consistently for every change.
A useful rule is that a security failure should enter the same visible workflow as a failed test. Developers should not need to search another system to discover that their change cannot be released.
Step 4: Separate Integration from Production Release
One of the most damaging CI/CD misunderstandings is assuming that continuous integration means continuous production deployment.
CI answers whether the change is suitable for merging. CD creates a controlled route for moving validated software through environments. Production is one stage in that route, not the automatic meaning of every successful build.
A practical environment model is:
- Development receives integrated changes quickly.
- Staging provides an environment for broader functional and business validation.
- Production receives only the version that passed previous gates and the required approval.
The workflow can be described as a set of explicit conditions:
IF pull request checks fail:
reject the merge
IF main branch build fails:
stop delivery
IF development tests fail:
do not promote to staging
IF staging validation fails:
do not approve production
IF production approval is missing:
wait
IF deployment succeeds:
begin operational monitoring
This structure protects production without giving up frequent integration.
The team can merge code regularly, deploy it to controlled environments, and still prevent premature exposure to end users.
Step 5: Use Feature Flags for Incomplete Features
A pull request controls whether code enters the main branch. A feature flag controls whether a capability is available at runtime.
These are not competing techniques.
A long-lived branch may keep unfinished code away from production, but it also increases the difference between the branch and the main codebase. That makes later integration harder.
A feature flag allows the code to be integrated and deployed while the capability remains disabled.
A small C# abstraction keeps the decision visible in application code:
public interface IBookingFeatures
{
bool AirportTransferEnabled { get; }
}
public sealed class BookingFeatures : IBookingFeatures
{
public bool AirportTransferEnabled { get; init; }
}
The application can check the flag before presenting or executing the new workflow:
public sealed class TransferOptionService
{
private readonly IBookingFeatures _features;
public TransferOptionService(IBookingFeatures features)
{
_features = features;
}
public bool CanOfferTransfer()
{
return _features.AirportTransferEnabled;
}
}
The important design rule is that the feature must remain safe while disabled. Hiding only a button is not enough if the underlying endpoint can still be called.
The flag should control the complete user-facing capability, including any server-side path that would expose unfinished behavior.
Feature flags also need ownership. Each flag should have:
- A clear purpose
- A default state
- An owner
- A plan for enabling it
- A plan for removing it after the rollout is complete
Without cleanup, old flags become permanent branches inside the codebase.
Step 6: Test the Deployed Behavior
Unit tests validate isolated pieces of logic. They cannot prove that the complete deployed workflow works across the web application, configuration, data access, and environment.
After deployment to a non-production environment, run functional tests against the application as a user or client would interact with it.
For the booking scenario, functional validation can cover:
- Creating a booking without an airport transfer
- Confirming that the disabled transfer feature is not available
- Enabling the flag in a controlled environment
- Creating a booking with the transfer option
- Verifying the final price
- Confirming that the booking remains usable after the feature is disabled again
This matters because an application can pass build-time tests and still fail after deployment due to environment configuration, service connectivity, or integration behavior.
The pipeline should stop promotion when functional validation fails.
Step 7: Require Approval Before Production
Full automation does not mean removing every human decision.
Development and staging can be highly automated while production remains protected by an approval step. The approval confirms that technical validation has completed and that the release is appropriate for the business at that moment.
A production approval is especially valuable when:
- The deployment affects a critical customer workflow
- A feature requires coordinated communication
- The release must happen inside an agreed window
- The team needs confirmation from a product owner or operator
- A previous stage produced results that require human interpretation
The approval should not replace automated checks. It should occur after those checks have produced evidence.
Step 8: Close the Loop with Continuous Feedback
Delivery is not complete when the deployment job turns green.
Continuous Feedback uses production evidence to show whether the application is satisfying users and operating safely. For an Azure-hosted .NET application, Application Insights can provide information about requests, failures, database performance, and slow operations.
After enabling the transfer feature, the team should watch for changes such as:
- Increased request failures
- Slow booking operations
- Database calls taking longer than expected
- Exceptions in the new workflow
- Abnormal usage patterns
- Signs of instability after deployment
Operational data supports two decisions:
- Should the feature remain enabled?
- What should the team improve next?
A feature flag makes the first response faster because the team can disable the capability without waiting for a new code deployment.
Monitoring also supports incident investigation. Instead of relying only on user complaints, developers and operators can examine actual application behavior soon after a change reaches production.
Define the Gates Before Writing the Workflow
A GitHub Actions file can automate almost any sequence, but automation should implement an agreed process rather than invent one.
Before writing the workflow, define the gates in a delivery table:
| Stage | Required evidence | Failure response |
|---|---|---|
| Pull request | Build, tests, review, security checks | Block merge |
| Development | Successful deployment and integration checks | Stop promotion |
| Staging | Functional tests and business validation | Reject release |
| Production | Required approval | Wait for approval |
| Post-deployment | Healthy requests, errors, dependencies, and latency | Investigate or disable feature |
This makes the delivery rules understandable to developers, reviewers, operators, and product stakeholders.
It also prevents the pipeline from becoming a collection of jobs that nobody can explain.
Common Mistakes
Deploying every successful build directly to production
A successful build proves that compilation completed. It does not prove that the change is functionally complete, secure, approved, or healthy in production.
Using pull requests as feature release controls
Pull requests control code integration. They do not provide a runtime switch for functionality that has already been deployed.
Adding security at the end
Late security review turns security findings into release emergencies. Run repeatable checks during normal development and CI.
Trusting only unit tests
Unit tests are necessary, but deployed systems also need integration and functional validation in realistic environments.
Creating approvals without evidence
An approval button is weak when the approver cannot see test results, security findings, and staging validation.
Monitoring without a response plan
Collecting telemetry is not continuous feedback unless the team uses it to investigate incidents, improve the system, or control a release.
Leaving feature flags forever
Temporary release controls become maintenance problems when nobody removes them after the feature is stable.
Delivery Checklist
- [ ] Direct pushes to the main branch are disabled
- [ ] Pull requests require automated checks
- [ ] Peer review covers tests, security, and feature flag usage
- [ ] CI restores, builds, tests, and publishes the .NET application
- [ ] Static quality and security checks can block unsafe changes
- [ ] Development, staging, and production are separate delivery stages
- [ ] Functional tests run after deployment to a controlled environment
- [ ] Production requires the appropriate approval
- [ ] Incomplete features remain disabled through runtime flags
- [ ] Disabled features are also protected on the server side
- [ ] Production telemetry is reviewed after deployment
- [ ] The team has a response plan for failures and abnormal behavior
- [ ] Temporary feature flags have owners and removal plans
Conclusion
A fast pipeline is useful only when it preserves control.
Pull requests protect the main branch. CI validates each change. Continuous Security moves security checks into the same development path. Staged delivery and approvals protect production. Feature flags separate code deployment from feature exposure. Functional tests verify the deployed workflow, and Application Insights provides the production evidence needed to respond after release.
Together, these controls let a .NET team integrate frequently without treating every successful build as permission to expose unfinished or unsafe behavior to customers.