On CI, which build_app options decide where an iOS build's artifacts, derived data and logs land?
answer
- defaults suit a laptop, not a job
- one option per artifact kind
- derived data is not the output directory
- keep the archive for its dSYMs
- buildlog_path and result_bundle save the evidence
basics
~10 sIn fastlane's build_app, derived_data_path places Xcode's intermediates, build_path and archive_path place the .xcarchive, output_directory and output_name place the exported .ipa, buildlog_path saves the raw xcodebuild log, and result_bundle writes the .xcresult.
solid answer
~40 sEvery one of `build_app`'s path options defaults to somewhere convenient for a laptop and inconvenient for an unattended job. Pin them under the checkout: `derived_data_path` for Xcode's intermediates, `build_path` and `archive_path` for the `.xcarchive`, `output_directory` plus `output_name` for the exported `.ipa`, `buildlog_path` for the raw `xcodebuild` log, and `result_bundle` with `result_bundle_path` for the `.xcresult` bundle. Which of those you *keep* is the judgment: for our lighthouse maintenance app the release job retains the `.ipa` to ship, the `.xcarchive` because it carries the dSYM bundles a crash report will need months later, and the log plus `.xcresult` to explain a failure — and discards the derived data. `clean` is the other decision, trading a guaranteed-fresh archive against a full recompile.
code
ruby · 16 lineslane :build_ios_ci do
build_app(
scheme: "LighthouseMaintenance",
configuration: "Release",
export_method: "app-store",
clean: true,
derived_data_path: "build/derived-data",
build_path: "build/archives",
archive_path: "build/archives/LighthouseMaintenance.xcarchive",
output_directory: "build/artifacts",
output_name: "LighthouseMaintenance.ipa",
buildlog_path: "build/logs",
result_bundle: true,
result_bundle_path: "build/artifacts/LighthouseMaintenance.xcresult"
)
endgo deeper
Learn which option places which artifact - output_directory and output_name for the .ipa, archive_path for the .xcarchive, derived_data_path for Xcode's intermediates, buildlog_path for the raw log.
Explain what derived data actually is and why leaving it in a shared per-user location makes an unattended iOS build hard to reason about. Know that dSYMs come from the archive, not from the .ipa.
Show the retention decision - which artifacts survive the job and why, when clean earns its recompile cost, and how a failed run still leaves enough evidence to diagnose without a rebuild.
Own the policy across apps: a standard artifact layout, archive retention tied to how long releases stay supported, and a rule that no build ships whose dSYMs were not kept.
## Why paths are a lane decision, not a detail By default Xcode writes its intermediates into a shared per-user derived-data location, and `build_app` (alias `gym`) writes the export relative to the working directory. On a developer's Mac that is invisible and fine. In a lane that a job runs unattended, each of those defaults is somewhere an artifact can be lost, mixed with a neighbouring job's output, or left behind. Pinning them costs a few lines and is the difference between "the iOS build failed" and "here is the log, the archive and the `.xcresult` from the run that failed". ## The options that place things - `derived_data_path` — where Xcode's **derived data** goes: module caches, intermediate object files, the index. Point it inside the checkout so it is scoped to this build, is easy to collect or discard deliberately, and is not silently shared with whatever ran before. - `build_path` — the directory `build_app` uses for the build products it manages, including where an archive is created when you have not named one explicitly. - `archive_path` — the exact `.xcarchive` path. This is the artifact worth keeping, because the archive carries the **dSYM** debug-symbol bundles. - `output_directory` and `output_name` — the exported package's directory and filename. Deterministic names are what an upload step or an artifact glob depends on. - `buildlog_path` — where the raw `xcodebuild` log is written, rather than only scrolling past in a job console that may be truncated. - `result_bundle` and `result_bundle_path` — write Xcode's `.xcresult` bundle, which carries structured build results a viewer can open long after the console output is gone. ## The options that shape the log - `xcodebuild_formatter` selects the formatter that pretty-prints `xcodebuild` output; `disable_xcpretty` turns that formatting off so you get raw output; `silent` suppresses fastlane's own chatter. - On an unattended run, prefer readable output **plus** a saved `buildlog_path` over a quiet run you cannot reconstruct afterwards. - `analyze_build_time` and `build_timing_summary` add timing output. Worth turning on deliberately when deciding whether an iOS build is slow because of the machine or because of the project — and worth turning off again. ## Two judgment calls 1. **`clean`.** `clean: true` guarantees the archive owes nothing to a previous run, at the cost of a full recompile every time. Where each job starts from a fresh checkout there is little left to be dirty and `clean` mostly buys compile time you did not need to spend; where the same machine keeps derived data between runs it buys correctness. Decide from what your build machine actually is — and pin `derived_data_path` either way, so what is being reused is explicit. 2. **What you keep.** Retention is a real cost, so be deliberate. For the lighthouse maintenance app's release job that is the `.ipa` (to ship), the `.xcarchive` including its dSYMs (to symbolicate), and the `buildlog_path` output plus the `.xcresult` (to explain a failure). The derived-data directory is the one large thing worth discarding. ## The dSYM point, stated plainly A **dSYM** is Apple's debug-symbol bundle: a map from the addresses inside a compiled binary back to file and line. Every archive of an iOS app produces fresh ones, and they match only the exact binary they were produced with. If you keep the `.ipa` and discard the archive, a crash report from that build stays a list of hexadecimal addresses forever, and no rebuild reproduces the mapping reliably. `include_symbols` controls whether symbols travel with the exported package; retaining the archive itself is what actually guarantees you can symbolicate later. ## A worked shape for the lane - Pin `derived_data_path`, `build_path` and `archive_path` under the checkout, not under a per-user directory. - Give `output_directory` a fixed name and `output_name` a stable filename the artifact step can find without guessing. - Set `buildlog_path` unconditionally, including on green builds — the run you wish you had a log for is always one you already threw away. - Turn on `result_bundle` and place it with the other artifacts. - Publish the `.ipa`, the `.xcarchive` and the logs before the lane's upload step runs, so a failed upload still leaves you a binary to inspect. - Let `upload_to_testflight` (`pilot`) read the path out of `Actions.lane_context[SharedValues::IPA_OUTPUT_PATH]` rather than reconstructing the filename in a second place that can drift. The senior framing: `build_app`'s path options are not tidiness, they are the difference between a build system that can explain itself and one that can only be re-run. Pin every path, decide deliberately what survives the job, and never let the dSYMs be the thing you discarded.
- Why keep the .xcarchive rather than only the exported .ipa?The archive carries the dSYM debug-symbol bundles for that exact binary, and only those bundles turn a crash report's addresses back into file and line. It also lets you re-export the same build for another channel without recompiling. The `.ipa` alone leaves you a shippable artifact you cannot later explain.
- Is clean: true always the right setting for an iOS build lane?No. It guarantees freshness by recompiling everything, which is wasted time when each job already starts from a fresh checkout with no derived data to reuse. It earns its cost on a machine that persists state between runs. Either way, pin `derived_data_path` so what is being reused is explicit rather than inherited.
saying these in an interview costs you the question
- Leaves every path at its default and hunts for artifacts afterwards
- Keeps only the .ipa and discards the archive with its dSYMs
- Thinks derived_data_path is where the .ipa is written
- Saves the log only when the build already failed
- Assumes clean: true is free because the build looks fast locally