A practical Windows-first iOS workflow using GitHub, Vercel, Neon, Capacitor, App Store Connect CLI, Xcode Cloud and TestFlight. We tried and tested this and below is the approach. -Shovan, SCube
Can you build an iOS app on Windows? Yes, with an important qualification.
You can do most application development, testing, backend work, infrastructure management, App Store Connect configuration and release orchestration from Windows.
What you cannot move to Windows is Apple's native iOS compilation and signing toolchain.
The solution is to move that part somewhere else.
Instead of making every developer use a Mac, use Xcode Cloud as the Mac that builds the application.
The result looks like this:
Windows developer
│
│ Git / CLI
▼
GitHub
│
├──────────────► Vercel Preview
│ │
│ ▼
│ Neon
│
▼
Xcode Cloud
Apple-hosted macOS
│
▼
TestFlight
│
▼
iPhone
There is still one Mac-specific step: the first Xcode Cloud workflow must be initialised using Xcode.
After that, the normal developer workflow can be Windows-first.
This guide shows the complete setup.
Table of contents
- What this architecture actually solves
- The tools we will use
- Install the CLI stack on Windows
- Create an isolated Git development branch
- Build the application normally on Windows
- Create a preview environment with Vercel
- Give the feature its own Neon database
- Connect the preview deployment to the test database
- Create the Apple resources from the command line
- Prepare the iOS project for cloud builds
- The one-time Mac handoff
- Configure Xcode Cloud and TestFlight
- Manage iOS delivery from the CLI after bootstrap
- The daily Windows developer workflow
- Security and isolation rules
- Common problems
- FAQ
1. What this architecture actually solves
The usual assumption is:
iOS development requires every developer to own and work from a Mac.
That is no longer the most useful way to think about the problem.
There are really several different jobs hidden inside "building an iOS app":
| Job | Needs macOS? |
|---|---|
| Write React/TypeScript/web code | No |
| Build APIs | No |
| Run automated web tests | No |
| Work with Git/GitHub | No |
| Configure Vercel | No |
| Create/manage Postgres environments | No |
| Manage many App Store Connect resources | No |
| Create Preview deployments | No |
| Native iOS compilation | Yes |
| iOS code signing | Yes |
| Local iOS Simulator | Yes |
| Native debugging in Xcode | Yes |
| TestFlight installation on an iPhone | No developer Mac required |
The goal is therefore not Mac-free iOS development.
A better description is:
Windows-first iOS development with macOS compilation delegated to Xcode Cloud.
That distinction matters.
We are not trying to work around Apple's platform.
We are trying to stop Apple's platform from determining which computer every application developer must use.
2. The tools we will use
This guide uses:
| Layer | Tool |
|---|---|
| Source control | Git |
| GitHub operations | GitHub CLI (gh) |
| Web/API deployment | Vercel CLI |
| Database | Neon CLI |
| Web-to-native runtime | Capacitor |
| Apple account automation | App Store Connect CLI (asc) |
| Native build | Xcode Cloud |
| Beta distribution | TestFlight |
Vercel and Neon are optional.
If your app uses another hosting platform or database, substitute those layers.
The core pattern is:
Git
↓
Cloud build
↓
Apple signing
↓
TestFlight
The Vercel + Neon combination simply gives us a particularly clean way to create isolated Preview infrastructure for each feature branch.
A note about App Store Connect CLI
In this guide, asc refers to the open-source App Store Connect CLI project:
https://github.com/rorkai/App-Store-Connect-CLI
It is a community-maintained tool, not Apple's official CLI.
It provides a command-line interface for many App Store Connect, TestFlight, signing and Xcode Cloud operations.
3. Install the CLI stack on Windows
Start with PowerShell or Windows Terminal.
Git
If you do not already have Git:
winget install --id Git.Git -e
Check:
git --version
GitHub CLI
winget install --id GitHub.cli -e
Authenticate:
gh auth login
Verify:
gh auth status
You can now create PRs, inspect CI, work with issues and call GitHub APIs without opening the browser.
For example:
gh pr status
gh pr list
gh repo view
Vercel CLI
Assuming Node.js is installed:
npm install -g vercel
Authenticate:
vercel login
Verify:
vercel whoami
Neon CLI
npm install -g neonctl
Authenticate:
neonctl auth
Check your projects:
neonctl projects list
App Store Connect CLI
Check whether the WinGet package is currently available:
winget search asc
If Rorkai.ASC is listed:
winget install --id Rorkai.ASC --exact
Otherwise, use the signed Windows release binary from the project's GitHub Releases page.
Verify:
asc version
asc --help
At this point the Windows machine has a command-line interface to almost every platform involved in delivery.
4. Create an isolated Git development branch
Do not start by modifying your release branch.
git switch main
git pull
git switch -c feature/new-ios-capability
Push it:
git push -u origin feature/new-ios-capability
If you or your coding agents regularly work on several features concurrently, use Git worktrees.
git worktree add ../new-ios-capability feature/new-ios-capability
Inspect them:
git worktree list
A worktree gives each feature or agent an independent checkout while keeping everything connected to the same Git repository.
That is especially useful for agent-assisted development:
Agent A → worktree A → feature A
Agent B → worktree B → feature B
Developer → main checkout
There is much less chance of two processes rewriting the same working directory.
5. Build the application normally on Windows
This example assumes a web-first application wrapped for iOS using Capacitor.
A typical stack might be:
React / Vue / Svelte
│
▼
Web build
│
▼
Capacitor
│
▼
iOS app
Install dependencies:
npm install
Run locally:
npm run dev
Run tests:
npm test
Build:
npm run build
If Capacitor is not already installed:
npm install @capacitor/core @capacitor/cli
npm install @capacitor/ios
Initialise it:
npx cap init
Add iOS:
npx cap add ios
After building your web app, copy the bundle into the native project:
npm run build
npx cap copy ios
The native iOS project should live in Git.
Do not treat the Mac developer's laptop as the only place where the native project exists.
Your repository should contain everything Xcode Cloud requires to reproduce the build.
If you use React Native, Flutter or a native iOS project instead of Capacitor, the exact build commands change, but the GitHub → Xcode Cloud → TestFlight architecture still applies.
6. Create a preview environment with Vercel
Link the local directory to your Vercel project:
vercel link
If your GitHub repository is connected to Vercel, pushing a feature branch can automatically create a Preview deployment.
You can also deploy manually:
vercel
Do not use:
vercel --prod
for experimental feature work.
The goal is:
main
↓
Production
feature/new-ios-capability
↓
Preview
Each feature branch should have a safe place to run without affecting your production application.
Verify:
vercel list
7. Give the feature its own Neon database
Do not point experimental application code at your production database simply because it is convenient.
There are two useful isolation levels.
Option A: Neon database branch
For normal feature development, Neon branching works well.
Link the workspace once:
neonctl link
Then create or select a database branch:
neonctl checkout feature-new-ios-capability
Pull its environment:
neonctl env pull
Now:
Git branch
feature/new-ios-capability
↕
Neon branch
feature-new-ios-capability
This creates a useful branch-first development model.
Option B: completely separate Neon project
For high-risk experiments, protocol changes, migrations or release laboratories, stronger isolation may be appropriate.
Create a new project:
neonctl projects create
This gives you a completely separate Postgres project and connection string.
A practical rule:
Normal feature
→ database branch
Risky migration / experimental app
→ separate database project
Production data should never become the accidental test environment.
8. Connect the preview deployment to the test database
Now connect the feature-specific Vercel deployment to the feature-specific database.
For example:
vercel env add DATABASE_URL preview feature/new-ios-capability
The CLI will prompt for the value.
For secrets, prefer the interactive prompt rather than putting the credential directly in the command because shell history is very good at remembering things you wish it had forgotten.
Verify the configuration:
vercel env ls preview feature/new-ios-capability
Pull the exact Preview configuration locally if required:
vercel env pull .env.preview.local \
--environment=preview \
--git-branch=feature/new-ios-capability
You now have:
feature/new-ios-capability
│
├── Vercel Preview
│
└── isolated Neon database
This principle is more important than the products being used:
Isolate code, deployment and data together.
A Git branch alone is not an isolated environment if it still writes to production infrastructure.
9. Create the Apple resources from the command line
This is where the workflow becomes considerably more interesting.
Many operations that traditionally require Developer Portal or App Store Connect browser sessions can now be orchestrated from the terminal.
Create an App Store Connect API key
In App Store Connect, create a Team API Key with the minimum role your workflow requires.
You will receive:
Key ID
Issuer ID
AuthKey_XXXXXXXXXX.p8
The .p8 file is a private credential.
Never:
commit it
paste it into a ticket
paste it into an AI conversation
store it in your repository
Authenticate asc:
asc auth login \
--name "Company App Store Connect" \
--key-id "<KEY_ID>" \
--issuer-id "<ISSUER_ID>" \
--private-key "<PATH_TO_AUTH_KEY.p8>" \
--network
Validate the setup:
asc auth status --validate
asc auth doctor
You want the authentication check to succeed before creating anything.
Create the Bundle ID
Instead of opening Certificates, Identifiers & Profiles manually:
asc bundle-ids create \
--identifier "com.example.product.lab" \
--name "Product Lab" \
--platform IOS
Check:
asc bundle-ids list --output table
Your new Bundle ID should appear.
Authenticate the Apple web session
Some operations are performed by asc through its Apple web-session workflow rather than the normal App Store Connect API.
Authenticate:
asc web auth login \
--apple-id "developer@example.com"
You may be prompted for password and two-factor authentication.
Check:
asc web auth status
Create the App Store Connect app record
Now create the actual App Store Connect application:
asc web apps create \
--name "Product Lab" \
--bundle-id "com.example.product.lab" \
--sku "product-lab-2026" \
--primary-locale "en-GB" \
--platform IOS \
--apple-id "developer@example.com"
Verify:
asc apps list \
--bundle-id "com.example.product.lab" \
--output table
Record the numeric App Store Connect application ID returned by Apple.
For example:
App ID: <APP_STORE_CONNECT_APP_ID>
We will use it later.
At this point we have created:
Git feature branch
+
Preview deployment
+
isolated database
+
Apple Bundle ID
+
App Store Connect application
without making the developer work through several unrelated dashboards.
10. Prepare the iOS project for cloud builds
Xcode Cloud does not build whatever happens to be sitting on somebody's Mac.
It clones your repository into a clean build environment.
That means the Git repository must contain enough native configuration to reproduce the application.
At minimum, review:
ios project/workspace
shared Xcode scheme
Info.plist
entitlements
package/dependency locks
Capacitor configuration
CI scripts
For Capacitor applications, a useful Xcode Cloud post-clone script might perform:
#!/bin/sh
set -e
npm ci
npm run build
npx cap copy ios
The important idea is:
Xcode Cloud should rebuild the web assets from the same Git commit that it is compiling.
Do not rely on somebody remembering to manually copy the latest frontend build into Xcode.
If the script is created on Windows, remember that shell scripts in Git may also need their executable bit set:
git update-index --chmod=+x ci_scripts/ci_post_clone.sh
Commit it:
git add .
git commit -m "Add Xcode Cloud build pipeline"
git push
11. The one-time Mac handoff
Here is the remaining Apple boundary.
The first Xcode Cloud workflow must be configured using Xcode on a Mac.
This does not mean the Windows developer has to move their work to the Mac.
The Mac operator is simply bootstrapping Apple's cloud build system.
The Windows developer hands over:
Repository
Branch
Bundle ID
App Store Connect App ID
Scheme
required environment variables
The Mac operator then:
- Pulls the same repository and branch.
- Opens the
.xcodeprojor.xcworkspace. - Signs into the correct Apple Developer team.
- Confirms the Bundle ID.
- Confirms the correct shared Xcode scheme.
- Opens Xcode Cloud setup.
- Selects the product/scheme.
- Grants Xcode Cloud access to the GitHub repository.
- Selects the correct branch.
- Starts the first build.
That is the one-time bootstrap.
The Windows developer does not need to hand over their application implementation.
Everything is already in Git.
12. Configure Xcode Cloud and TestFlight
For an internal development build, keep the workflow deliberately narrow.
For example:
Workflow
Internal Lab
Scheme
ProductLab
Branch
feature/new-ios-capability
Action
Archive iOS
Distribution
Internal TestFlight
Avoid accidentally pointing an experimental workflow at your production branch.
If your application needs environment variables during its build, configure them on this workflow.
For example:
APP_ENV=lab
API_ORIGIN=https://<preview-deployment>
FEATURE_LAB=1
The exact values depend on your application.
A good test-build configuration should fail closed.
For example:
Missing test API configuration
↓
disable sync / API functionality
is safer than:
Missing test API configuration
↓
silently use production
Once Xcode Cloud completes the archive, it can distribute the build directly through TestFlight.
You should not need to manually:
archive
export IPA
upload IPA
wait for processing
assign build
for every change.
13. Manage iOS delivery from the CLI after bootstrap
Once the first Xcode Cloud product exists, return to the command line.
Inspect it:
asc apps ci-product view \
--id "<APP_STORE_CONNECT_APP_ID>" \
--output json \
--pretty
List workflows:
asc xcode-cloud workflows list \
--app "<APP_STORE_CONNECT_APP_ID>" \
--output table
Trigger a build:
asc xcode-cloud run \
--app "<APP_STORE_CONNECT_APP_ID>" \
--workflow "Internal Lab" \
--branch "feature/new-ios-capability" \
--wait
Inspect the result:
asc xcode-cloud build-runs list \
--workflow-id "<WORKFLOW_ID>"
This is where the Mac stops being the team's build machine.
Apple's cloud infrastructure becomes the build machine.
14. The daily Windows developer workflow
After the platform has been bootstrapped, an ordinary Windows developer's day becomes surprisingly conventional.
git pull
git switch feature/new-ios-capability
npm install
npm run dev
# write code
npm test
git add .
git commit -m "Implement capability"
git push
The rest can happen automatically:
git push
│
▼
GitHub
│
├─────────────► Vercel Preview
│ │
│ ▼
│ isolated DB
│
▼
Xcode Cloud
│
├── install dependencies
├── build web application
├── copy native assets
├── compile iOS application
├── sign
└── archive
│
▼
TestFlight
│
▼
iPhone
The developer tests the actual iOS application through TestFlight.
A local Mac is no longer part of every feature cycle.
15. Security and isolation rules
The tooling is useful, but the architecture matters more than the commands.
These are the rules I would keep.
1. Never make production the default fallback
Bad:
TEST_API_URL missing
↓
use PROD_API_URL
Better:
TEST_API_URL missing
↓
fail build or disable network functionality
2. Isolate more than Git
A real test environment may need:
separate Git branch
separate deployment
separate database
separate Bundle ID
separate iOS sandbox
separate TestFlight application
A Git branch connected to the production database is not isolated.
3. Keep credentials outside the repository
Do not commit:
.p8 keys
database passwords
Vercel tokens
Apple passwords
API keys
Use the relevant platform's secret/environment system.
4. Give CI the minimum required permissions
An automated build normally does not need unrestricted access to every production resource.
Use scoped credentials and separate test infrastructure where practical.
5. Keep native configuration in Git
Your Mac operator should not possess invisible project knowledge.
If a scheme, entitlement or CI script is required to build the application, it should normally be represented in source control.
6. Treat TestFlight as part of CI/CD
TestFlight should not be the final manual chore performed by one person.
It can be the output of the pipeline.
16. Common problems
no Xcode Cloud product found
If you run something similar to:
asc xcode-cloud workflows list \
--app "<APP_ID>"
and receive:
no Xcode Cloud product found
before the first Xcode Cloud setup, that is expected.
Complete the one-time workflow setup using Xcode on a Mac and start the first build.
Then retry the CLI.
App works locally but fails in Xcode Cloud
Remember that Xcode Cloud starts with a clean environment.
Check that:
dependencies are installed
runtime versions are compatible
required configuration is committed
build scripts are committed
environment variables exist
dependency lockfiles exist
the Xcode scheme is shared
If your build only works because of something installed manually on one developer's Mac, the CI build has exposed useful information.
Preview app connects to production
Stop.
Do not solve this by continuing to test carefully.
Fix the architecture.
Check:
branch-specific environment variables
API origin
database connection string
runtime environment detection
fallback behaviour
Your experimental build should be structurally unable to use production by mistake.
TestFlight contains old frontend code
If you wrap a web application with Capacitor, make the cloud build rebuild the web application before Xcode compiles it.
For example:
npm ci
npm run build
npx cap copy ios
Do not assume previously generated native assets are current.
17. FAQ
Can I build an iOS app on Windows?
Yes, most application development can be performed on Windows. Apple's iOS compiler, signing tools and Xcode still require macOS, so use a Mac or a macOS cloud build environment such as Xcode Cloud for the native build.
Do I need a Mac for iOS development?
You do not necessarily need a Mac for every developer or every development task. You still need access to macOS for the initial Xcode Cloud setup, native debugging, the iOS Simulator and some Xcode-specific configuration.
Can Xcode Cloud build an iOS app from GitHub?
Yes. Xcode Cloud connects to your Git repository, checks out the source code, resolves dependencies, executes configured scripts and runs Xcode build/archive actions in Apple's cloud environment.
What is Xcode Cloud?
Xcode Cloud is Apple's CI/CD platform for Apple-platform applications. It runs builds and tests in cloud-hosted macOS environments and integrates with Xcode, App Store Connect and TestFlight.
Can a Windows developer publish to TestFlight?
A Windows developer can drive much of the workflow from Git and CLI tools. Xcode Cloud performs the macOS build and signing, after which the build can be delivered to TestFlight without the Windows developer manually archiving it on a Mac.
Can Xcode Cloud run without my Mac being online?
Yes. Once the repository and workflow are configured, Apple's cloud infrastructure performs the build. Your local Mac is not acting as the build server.
Can I use Capacitor for iOS development on Windows?
Capacitor allows most of the shared web application to be developed with normal web tooling on Windows. Native iOS compilation and native debugging still require Apple's macOS/Xcode environment.
Does this work with React Native or Flutter?
The same delivery architecture can. The application-specific build commands are different, but GitHub, isolated cloud environments, Xcode Cloud and TestFlight can serve the same roles.
Is App Store Connect CLI an official Apple tool?
No. The asc tool used in this guide is an open-source third-party CLI for App Store Connect. Evaluate it like any other dependency before adopting it in a production release process.
Should every feature get its own database?
Not necessarily. Database branches are often sufficient for normal feature work. For high-risk experiments, migrations or security-sensitive testing, a completely separate database project can provide stronger isolation.
The bigger idea
The interesting part of this workflow is not any individual CLI.
GitHub CLI has existed for years.
Cloud databases have existed for years.
CI/CD has existed for years.
The change is that these systems can now be treated as one programmable development control plane.
Developer or coding agent
│
├── Git
├── GitHub
├── preview infrastructure
├── database
├── Apple configuration
├── cloud build
└── beta distribution
A coding agent can operate the same commands a developer can.
That changes the role of the human.
Instead of:
open dashboard
click settings
copy value
open another dashboard
create environment
open another dashboard
upload build
wait
repeat
the human can concentrate on:
architecture
constraints
security
review
device validation
release approval
The workflow still works without AI.
Agents simply make the programmable interface much more valuable.
Final architecture
Windows developer
│
code + tests
│
▼
GitHub
│
┌──────────┴───────────┐
│ │
▼ ▼
Vercel Preview Xcode Cloud
│ │
▼ │
isolated database │
▼
signed iOS build
│
▼
TestFlight
│
▼
iPhone
The Mac has not disappeared.
It has stopped being the centre of the development workflow.
And for teams where most developers use Windows, that is a much more important improvement.
[build ios app on windows] [xcode cloud]





Top comments (0)