Fail-fast means detecting a condition that prevents correct execution and reporting it immediately rather than continuing with invalid assumptions. In cloud engineering, this reduces wasted build capacity, shortens developer feedback loops, and limits the impact of defective changes. The principle applies to application startup, automated testing, deployment validation, and requests between distributed components.
In a CI/CD pipeline, place inexpensive, high-value checks before costly or consequential operations. Validate configuration, compile code, and run unit tests before provisioning integration environments or deploying a release. If a required check fails, the pipeline should mark the relevant action as failed and prevent dependent deployment actions from proceeding. Independent tests may still run in parallel when collecting additional diagnostic information is useful.
Process exit codes provide a practical machine-readable contract. Under the usual shell convention, an exit code of zero means successful completion, while a nonzero code indicates failure. A test runner should return nonzero when required tests fail. A smoke-test script can call an application endpoint, check the response and required dependencies, and exit with zero only when its acceptance criteria pass. Merely printing an error or writing a failed test report does not reliably fail a pipeline if the command still reports success.
Preserve failure status through every wrapper. A script that runs a failing test command and then successfully prints a completion message can accidentally return the final command's successful status. Similarly, piping test output into a logging command can hide the test runner's failure when the shell reports only the last command's status. Explicitly check and propagate exit codes, or use suitable shell controls. In Bash, enabling pipefail helps preserve failures within command pipelines; automatic error-exit behavior has exceptions and is not a substitute for deliberate error handling.
Application exit codes are useful health signals, but they are not continuous health measurements. A long-running process can remain alive while serving errors, deadlocking, or losing access to a critical dependency. Conversely, a one-time process can exit successfully without proving that the deployed application is healthy. Combine startup validation and process termination status with readiness checks, synthetic requests, and operational metrics. A health-check command can translate an observed application failure into a nonzero exit code, but its checks must actually exercise the behavior that matters.
For example, reject a deployment immediately when a required configuration value is missing. After deployment to a test environment, execute a bounded smoke test that verifies a representative request and returns nonzero if the result is incorrect. Preserve logs and test reports even when the test fails, while preventing the release from advancing. Cleanup and diagnostic collection should remain possible after failure; fail-fast does not mean abandoning temporary resources.
The main trade-off is between rapid feedback and tolerance of transient conditions. A dependency may need a short startup period, so bounded retries, explicit timeouts, and readiness grace periods can be appropriate. Unlimited retries conceal persistent defects, while overly aggressive failure thresholds create false alarms and restart loops. Fail-fast also does not automatically mean rollback, immediate cancellation of every parallel task, or termination of an otherwise healthy service because an optional dependency is unavailable. Those behaviors require separate, explicit policies.