You have exported your Rork app, unzipped it on your machine, and you are ready to put it in your own GitHub repository.
Most people open the folder and immediately type git init. That single ordering choice costs more than anything else in this workflow.
The exported folder ships with files that should never leave your machine. And Git does not forget: once something lands in a commit, deleting it later does not remove it from history.
So I set up the two orderings side by side and measured what actually gets committed in each case. Three commands, and the difference is stark.
The exported folder mixes your app with things that must stay local
Unpack a Rork export and you get a mix of files you wrote and files you have never seen before.
| File or folder | What it is | Push to GitHub? |
|---|---|---|
app/ assets/ app.json |
Your actual app | Yes |
package.json package-lock.json |
Your dependency list and its pinned versions | Yes |
node_modules/ |
The dependencies themselves, rebuilt any time by npm install |
No |
.env .env.production |
API keys and endpoints | Never |
*.p8 *.jks *.keystore |
Signing credentials for App Store and Google Play submissions | Never |
google-services.json GoogleService-Info.plist |
Firebase connection details | No |
ios/ android/ |
Native projects generated by expo prebuild |
It depends (see below) |
.expo/ |
Local dev server cache | No |
Skipping node_modules/ is a size question. Skipping .env and your signing keys is a different category of problem.
If those reach a public repository even once, deleting them does not undo it. Your only real remedy is to issue new credentials.
With the ignore file first, only nine files were staged
Here is the "ignore file first" run.
I recreated the shape of a fresh export, then wrote .gitignore before touching git init:
node_modules/
.expo/
dist/
web-build/
ios/
android/
*.p8
*.p12
*.jks
*.keystore
*.mobileprovision
google-services.json
GoogleService-Info.plist
.env
.env.*
!.env.example
npm-debug.log*
yarn-error.log*
.DS_Store.gitignore is a plain text file that tells Git "anything matching these names does not need to be tracked." It lives at the top level of your project, spelled exactly like that.
Then git init, git add -A, and a look at what made it in:
$ git status --porcelain | sed 's/^A //' | sort
.gitignore
app.json
app/(tabs)/index.tsx
assets/images/icon.png
eas.json
expo-env.d.ts
package-lock.json
package.json
tsconfig.json
Nine files. No .env, no keys, no node_modules/.
If you want to know which line stopped which file, git check-ignore -v reports it:
$ git check-ignore -v .env node_modules/expo/package.json AuthKey_ABC123.p8
.gitignore:14:.env .env
.gitignore:1:node_modules/ node_modules/expo/package.json
.gitignore:7:*.p8 AuthKey_ABC123.p8
The left side is the line number in .gitignore, the right side is the file it caught. Useful whenever you add a rule and want to confirm it bites.
Reverse the order and the ignore file arrives too late
Now the other run, which is the one worth internalizing.
I committed first and thought about .gitignore afterwards. Six files were tracked:
$ git ls-files
.env
.expo/devices.json
AuthKey_ABC.p8
app.json
node_modules/expo/package.json
package.json
Your .env and your .p8 are now part of the repository.
Noticing the mistake, I wrote a proper .gitignore and committed again. Nothing changed:
$ git ls-files | grep -E '\.env|node_modules|\.expo|p8'
.env
.expo/devices.json
AuthKey_ABC.p8
node_modules/expo/package.json
All four still tracked. .gitignore only governs files Git is not yet tracking. Once tracking begins, the rules no longer reach them.
Untracking has to be explicit:
git rm -r --cached node_modules .expo .env AuthKey_ABC.p8
git commit -m "Stop tracking local-only files"--cached leaves the files on disk and removes them from Git's index only. That dropped the tracked set to three files.
But here is the part that matters. Earlier commits still hold everything:
$ git cat-file -p HEAD~2:.env
(the contents of the .env you "deleted", printed back at you)
So a key that has been pushed to a public repository should be treated as compromised, no matter how carefully you clean up afterwards. At that point I rotate the credential before doing anything else. As an indie developer you are the only holder of that key, so nobody else is going to catch it for you. Every Key You Ship Is Public: Secret Boundaries and Rotation for Rork-Generated Apps walks through where to draw the boundary and how to rotate.
Get the order right and none of this cleanup exists. It is a three-minute difference.
Negation works on file patterns and fails under ignored directories
A line starting with ! in .gitignore means "this one is an exception, track it anyway."
It is handy, but it works in one situation and silently does nothing in another. I ran both.
The working case: block every .env.* variant, then let the shareable template through.
.env
.env.*
!.env.exampleAfter git add -A:
$ git diff --cached --name-only
.env.example
.gitignore
app.json
.env.example is in. .env.local and .env.production are not. Exactly as intended.
The failing case: ignore ios/ wholesale, then try to keep ios/Podfile.
ios/
!ios/Podfile$ git diff --cached --name-only
.gitignore
app.json
No ios/Podfile. The syntax is fine, and Git simply skips it.
| Pattern | How the block is written | Outcome |
|---|---|---|
.env.* plus !.env.example |
Blocked by filename pattern | Exception applies |
ios/ plus !ios/Podfile |
Blocked as a whole directory | Exception ignored |
The reason is that once Git excludes a directory, it stops descending into it. Never having looked inside, it never reads your exception for a file within.
To keep one file out of an otherwise unwanted folder, block the contents rather than the folder: ios/build/ and ios/Pods/ instead of ios/.
One more caution: do not judge this by git check-ignore -v alone. When a ! line matches, the tool reports that match, which reads as if the file were being handled. The reliable check is git add -A followed by git diff --cached --name-only.
Whether to commit ios/ and android/ depends on how you build
This is the "it depends" row from the first table, and the answer genuinely varies.
One question settles it: are you hand-editing anything on the native side?
| Your situation | ios/ and android/ |
|---|---|
| You let Rork and EAS Build handle everything and have never opened Xcode or Android Studio | Ignore them; they are regenerated each time |
You configure everything through app.json and config plugins |
Ignore them |
| You edited Info.plist or build.gradle directly | Commit them; those edits cannot be reproduced otherwise |
When unsure, start by ignoring them. Changing your mind later means deleting two lines from .gitignore. Going the other direction, as we just saw, is the expensive one.
Related: running expo prebuild --clean over hand-edited native files discards those edits, which is covered in Find the native edits expo prebuild will erase before you upgrade to SDK 57.
The one-time sequence
Three steps, once per project:
# 1. Enter the exported folder and write .gitignore first
cd my-rork-app
cat > .gitignore <<'EOF'
node_modules/
.expo/
dist/
web-build/
ios/
android/
*.p8
*.p12
*.jks
*.keystore
*.mobileprovision
google-services.json
GoogleService-Info.plist
.env
.env.*
!.env.example
npm-debug.log*
yarn-error.log*
.DS_Store
EOF
# 2. Then initialize, and look at what is about to be recorded
git init
git add -A
git diff --cached --name-only
# 3. Commit only after confirming no .env and no keys in that list
git commit -m "Initial commit"Step 2 is the whole article. A few seconds of reading that list, and anything unfamiliar becomes one more line in .gitignore instead of a permanent entry in your history.
Once the repository is up, wiring EAS Build to GitHub Actions is the natural next move; Setting Up CI/CD for Rork Apps with GitHub Actions and EAS Build covers that setup.
For now, run git add -A and git diff --cached --name-only against your own project and see whether anything unexpected shows up. That check alone is enough.