skip to content

In a fastlane Fastfile, what does private_lane change about a lane, and when is it the right choice?

level: juniorimportance: should knowfreq 47%

answer

  1. not every lane is an entry point
  2. callable from lanes, not the command line
  3. keeps the public surface small
  4. not a security boundary

basics

~20 s

private_lane declares a lane that is not a command-line entry point; only other lanes in the same Fastfile can call it. Use it for shared helper steps so the file's invocable entry points stay few and obvious.

solid answer

~40 s

`private_lane :prepare_signing do |options| ... end` registers a real lane with one restriction: it **cannot be invoked from the command line**, so no CI step can call it directly. Other lanes in the same Fastfile call it exactly as they would any lane, by name with a hash of parameters. The point is interface control — the orchard spray-diary app's `beta` and `release` lanes are the two names a pipeline is allowed to type, while the signing preparation they share stays a helper outside that public surface. It also keeps the listed lanes meaningful, since helpers do not clutter the list of entry points. It is **not** a security boundary: the code is in the repository, and anyone can promote a private lane to a lane in one edit.

code

ruby · 12 lines
ruby
platform :ios do
  private_lane :prepare_spray_diary_signing do |options|
    sync_code_signing(type: options[:type] || "appstore")
  end

  desc "Build the orchard spray-diary app for Apple TestFlight"
  lane :beta do
    prepare_spray_diary_signing(type: "appstore")
    build_app(scheme: "SprayDiary")
    upload_to_testflight
  end
end

go deeper

for a junior

Be ready to say the one thing private_lane changes: no command-line entry point, still callable from other lanes in the file. Give one concrete helper you would mark private.

for a middle

Explain the interface reasoning — which lanes are the contract with the pipeline and which are implementation — and contrast a private lane with a plain Ruby helper method in the same file.

for a senior

Demonstrate that you use privacy to keep a release file re-runnable and reviewable, and be explicit that it is not access control, since the Fastfile ships in a repository anyone on the team can edit.

for a principal

Own the public lane surface as a versioned interface across many pipelines: adding an entry point is a commitment, so have a view on how that set is reviewed and kept small as teams multiply.

## What private_lane actually changes A Fastfile registers named blocks, and `lane` and `private_lane` both register one. The difference is a single property: **a private lane is not an entry point**. `fastlane ios prepare_signing` will not run a lane declared with `private_lane`; only another lane in the same Fastfile can call it, and it does so the ordinary way — by writing the lane's name as a method call with an optional hash of parameters. Everything else stays the same. A private lane takes the same `options` hash, runs the same actions in the same order, aborts the same way when an action raises, and participates in the same run as the lane that called it. ## Why a Fastfile wants them A release Fastfile grows a handful of steps that two or three lanes share. Without private lanes, one of three bad things happens: the steps get copied, or they get promoted to public lanes nobody should call directly, or the whole release collapses into one enormous lane. Marking the shared step private gives the file a deliberate shape: - The **public lanes are the contract** with the pipeline. For the orchard spray-diary app that might be exactly `beta` and `release` — two names a CI step is allowed to type. - The **private lanes are the implementation**, reused across those entry points and free to change without renegotiating anything with the pipeline. - The **listed lanes stay meaningful**, because helper steps are not competing for attention with the two names that matter. ## private_lane versus lane versus a plain Ruby method | | `lane` | `private_lane` | plain Ruby method | |---|---|---|---| | invocable from the command line | yes | no | no | | callable from another lane | yes | yes | yes | | receives the lane `options` hash | yes | yes | takes ordinary arguments | | treated by fastlane as a lane | yes | yes | no, it is invisible to fastlane | That last row is the reason to reach for `private_lane` rather than a helper method for anything meaningful: fastlane knows a private lane is a lane, names it in the run output, and applies the file's lane semantics to it. A plain method is fine for a small pure computation; a private lane is right for a sequence of actions. ## What private_lane is not - **It is not a security boundary.** The Fastfile ships in the repository. Anyone who can read the file can change one word and invoke the lane. - **It does not hide secrets.** Whatever the lane logs, it logs. Privacy of credentials is a matter of where they are stored and how they are printed, not of which keyword declared the lane. - **It does not run automatically.** A private lane runs only when another lane calls it. Anything that should happen around every lane belongs in the `before_all`, `before_each`, `after_all`, `after_each` or `error` hooks instead. - **It does not change failure semantics.** If a private lane's action raises, the calling lane aborts too — privacy changes who may start the lane, not what happens when it fails. ## A shape that works 1. Write the steps first as one long lane and get the release working end to end. 2. Find the sequences that two lanes both need — signing preparation, version stamping, artifact staging. 3. Extract each into a `private_lane` with a name that says what it produces, taking any variation as parameters. 4. Leave exactly the lanes a pipeline should invoke as public `lane` declarations, each with a `desc` line. 5. Re-read the list of public lanes as if you were the pipeline author: if a name would confuse them, it is either misnamed or should be private. ```ruby platform :ios do private_lane :prepare_spray_diary_signing do |options| sync_code_signing(type: options[:type] || "appstore") end lane :beta do prepare_spray_diary_signing(type: "appstore") build_app(scheme: "SprayDiary") end end ``` Here `sync_code_signing` — the canonical name of the action usually called `match` — and `build_app`, the canonical name behind `gym`, are both Apple-platform steps for the iOS build of the spray-diary app. The private lane wraps the first so both the beta and release entry points share it. ## The neighbouring keyword The Fastfile DSL also carries `override_lane`, which replaces a lane definition the file inherited from a shared Fastfile rather than declaring a new one. The two are easy to confuse because both control which definition a caller reaches, but they answer different questions: `private_lane` decides **who may invoke** a lane, and `override_lane` decides **which body wins** when a lane of that name already exists.

  • When would you use a plain Ruby method in the Fastfile instead of a private lane?
    For a small pure computation — deriving a build name, formatting a release note — where nothing runs actions and there is no value in fastlane treating it as a lane. Once the helper starts invoking actions, needs the lane `options` hash, or should appear as a step in the run output, make it a private lane so the file's structure matches what actually executes.
  • Does marking a lane private stop someone from running its steps on CI?
    No. It only removes the command-line entry point. The Fastfile is in the repository, so anyone who can change the file can make the lane public or call it from a public lane. Treat `private_lane` as an interface decision that keeps the invocable surface small and intentional, never as an access control.

saying these in an interview costs you the question

  • Thinks private_lane hides the code or protects its secrets
  • Believes a private lane runs automatically around other lanes
  • Cannot say who is allowed to call a private lane
  • Marks everything private and leaves no invocable entry point
  • Uses a plain Ruby method for a whole sequence of actions