Ship vibe-coded apps with zero-config CI/CD, Docker, and Cloud Run, Netlify, Cloudflare Workers or another of your choice deployment.
A monorepo framework that automates everything between code and production. Create a package, commit, push — it's deployed. Each package in the monorepo is published individually: a push only deploys new or changed packages, not everything.
- One push ≠ deploy everything — Only packages with actual changes are built and deployed. The rest stay untouched.
- Each package = its own pipeline — Every package gets a dedicated GitHub Actions workflow with path filters.
- Choose your infra — Cloud Run with auto-generated Docker + CI/CD, Netlify with just a config file, Cloudflare Workers for serverless backends, or bring your own. Per package.
- Zero config — Workflows, Dockerfiles, and README are auto-generated on commit. You just write code.
- AI-Powered setup — Connect your favorite AI (Claude, Gemini, Copilot) to auto-generate Dockerfiles tailored to your stack.
- Embed anywhere — Deploy frontend packages to Netlify or static hosts and embed them via
<iframe>in any system, site, or dashboard. - Dev + AI teamwork — Code with your team while AI handles boilerplate, tests, and deployment pipelines. Stay in control.
| Tool | Role |
|---|---|
| Bun | Runtime, bundler, test runner, package manager |
| TypeScript | Strict mode, no any |
| GitHub Actions | Auto-generated CI/CD per package |
| Docker | Auto-generated Dockerfiles (AI or default) |
| Cloud Run | Serverless container deploy |
| Netlify | Static/JAMstack deploy (no Docker needed) |
| Cloudflare Workers | Serverless backend deploy (no Docker needed) |
| Husky | Git hooks (pre-commit & pre-push) |
# clone & install
git clone https://github.com/opvibes/embark.git
cd embark
bun install
# setup (configures releases, upstream remote)
bun run setup
# create a new package (interactive)
bun run new-package
# commit — automations run automatically
git add . && git commit -m "feat: my new app"
# push — only changed packages deploy
git push origin mainForking? See Using as a Fork for how to pull upstream updates without re-introducing the demo package.
bun run new-packageThe CLI will ask for required fields:
- name — package name (camelCase or kebab-case)
- title — human-readable title (e.g. "My Awesome App")
- description — package description
- deploy target — Cloud Run, Netlify, Cloudflare Pages, Cloudflare Workers, or Other
Everything after the deploy target depends on it — each target only asks the questions that apply to it. Domain questions (custom domain, root domain, subdomain) are only asked for targets that actually manage a custom domain, and only when you say you want one. Cloud Run never asks them.
Then creates the complete structure:
packages/<name>/withsrc/index.ts,package.json,tsconfig.json.embark.jsoncwith all required config fieldsnetlify.toml(if Netlify was chosen)
Auto-adds to git. Just commit — pre-commit hooks handle workflows, Dockerfiles, and README.
The whole flow is Dockerfile → workflow → Cloud Run deploy. On push, the
workflow builds the image, pushes it to Artifact Registry and deploys to Cloud
Run. The service is served at its generated Cloud Run URL — Embark does not
configure a custom domain for GCP, so there is no subdomain, no rootDomain
and no Cloudflare step.
Two options for Netlify deploys:
Option 1: Manual deploy (default) — Connect the repo on Netlify and every push auto-deploys via Netlify UI.
// .embark.jsonc
{
"deploy": "netlify",
"name": "myApp",
"title": "My App",
"subdomain": "my-app",
"description": "My awesome application"
}Option 2: Workflow deploy — Generates GitHub Actions workflow for CI/CD automation with Cloudflare DNS.
// .embark.jsonc
{
"deploy": "netlify",
"workflow": "generate", // enables workflow generation
"name": "myApp",
"title": "My App",
"subdomain": "my-app",
"description": "My awesome application"
}With workflow: "generate", the workflow will:
- Build and deploy to Netlify
- Create subdomain on Cloudflare
- Register custom domain in Netlify
For targets that manage a custom domain (Netlify, Cloudflare Pages, Cloudflare Workers), a package is deployed to a subdomain (e.g. my-app.domain.com) by default. However, exactly one package in the monorepo can be deployed to the root domain (domain.com) instead. This does not apply to GCP, which is always served at its Cloud Run URL.
To enable root domain deployment, set rootDomain: true in .embark.jsonc:
// .embark.jsonc
{
"deploy": {
"appDeployment": "cloudflare-pages",
"workflowGen": true,
"cloudflareUse": true
},
"name": "myApp",
"title": "My App",
"description": "My main website",
"rootDomain": true // deploys to domain.com instead of my-app.domain.com
}
⚠️ Important constraints:
- Only ONE package can have
rootDomain: trueat a time- Assigning root domain to a second package will remove it from the first (with confirmation)
- The interactive CLI (
ensure-deploy-config) will warn you about consequences before confirmingsubdomainis not required whenrootDomain: true— the package is served at the root
When configuring packages via bun run new-package or during the pre-commit hook, the CLI will:
- Ask if you want a custom domain, and only then whether it should be the root domain
- Show a warning that only one package can use root domain
- If another package already has root domain, show two confirmation prompts before replacing it
- Automatically update the previous package's
.embark.jsoncto remove its root domain status
For serverless backends. Auto-generates a GitHub Actions workflow that deploys via wrangler deploy. No Docker needed — Workers runs your code at the edge.
// .embark.jsonc
{
"deploy": {
"appDeployment": "cloudflare-workers",
"workflowGen": true,
"cloudflareUse": true
},
"name": "myApi",
"title": "My API",
"subdomain": "api",
"description": "My serverless API"
}With cloudflareUse: true, the workflow will:
- Build and deploy via
wrangler deploy - Create CNAME record on Cloudflare DNS
- Configure custom domain on the Worker
Without custom domain (cloudflareUse: false), the worker is available at name.workers.dev.
For packages deployed elsewhere (Vercel, Fly.io, AWS, etc.). No workflow, no Dockerfile — you manage your own pipeline.
// .embark.jsonc
{
"deploy": "other",
"name": "myApp",
"title": "My App",
"subdomain": "my-app",
"description": "My awesome application"
}You can mix all targets in the same monorepo — APIs on Cloud Run or Workers, frontends on Netlify or Cloudflare Pages, custom infra elsewhere.
When generating Dockerfiles with AI, you can choose your favorite AI provider. Install any (or all) of these CLIs:
npm install -g @github/copilotcurl -fsSL https://claude.ai/install.sh | bashnpm install -g @openai/codexnpm install -g @google/gemini-cliUsage: When creating a new package or generating a Dockerfile, Embark will ask which AI provider you'd like to use. The CLI will send your package.json and file structure to the chosen provider, which will generate an optimized Dockerfile.
On git commit, these scripts run automatically:
| Order | Script | What it does |
|---|---|---|
| 1 | ensure-deploy-config.ts |
Prompts for missing required fields in .embark.jsonc (name, title, subdomain, description, deploy) |
| 2 | generate-workflows.ts |
Creates GitHub Actions workflow for new packages |
| 3 | sync-workflows.ts |
Syncs existing workflows with template (preserves # EMBARK:CUSTOM blocks) |
| 4 | cleanup-orphan-workflows.ts |
Removes workflows for deleted/external packages |
| 5 | generate-dockerfiles-ai.ts |
Generates Dockerfiles (AI or default) |
| 6 | update-readme-packages.ts |
Updates the packages table in README |
You can add custom steps to a generated workflow without losing them when the template is updated. Wrap your content in # EMBARK:CUSTOM markers:
steps:
- name: Checkout
uses: actions/checkout@v4
# EMBARK:CUSTOM
- name: My custom step
run: echo "this is never overwritten by sync"
# END EMBARK:CUSTOM
- name: Setup Bun
uses: oven-sh/setup-bun@v2Rules:
- Everything between the markers is preserved when
sync-workflowsruns - The sync compares the workflow without custom blocks against the template — if they match, nothing happens (no prompt, no overwrite)
- If the template changed, sync applies the update and re-inserts your custom block at the same position
- If the surrounding context line was removed from the new template, your custom block is appended at the end
Guaranteed preservation: wrap per-deploy customizations in
# EMBARK:CUSTOMblocks. Manual edits outside these blocks are preserved in most cases, but can be overwritten if they conflict with the same region changed by the template — in that conflict the template wins. Use# EMBARK:CUSTOMfor anything you cannot afford to lose.
On git push, the full test suite runs. Push is blocked if tests fail.
embark/
├── packages/ # each folder is an independent app
│ └── embark/ # this website
├── scripts/ # monorepo automations
│ ├── create-package.ts # interactive CLI to create packages
│ ├── embark-config.ts # shared deploy config reader
│ ├── ensure-deploy-config.ts # interactive prompt for missing/incomplete .embark.jsonc
│ ├── generate-workflows.ts # auto GitHub Actions per package
│ ├── generate-dockerfiles.ts # default Dockerfile generation
│ ├── generate-dockerfiles-ai.ts # AI-powered Dockerfile generation
│ ├── sync-workflows.ts # sync workflows with template
│ ├── cleanup-orphan-workflows.ts # remove orphaned workflows
│ ├── update-readme-packages.ts # auto-update README table
│ └── __tests__/ # tests for all scripts
├── templates/
│ └── workflow.template.yml # GitHub Actions base template
├── .github/workflows/ # auto-generated workflows (1 per package)
└── .husky/ # git hooks
| Script | Command | Description |
|---|---|---|
start |
bun start |
Unified interactive CLI — access all developer tools from one menu |
setup |
bun run setup |
Setup repo for personal use (configure releases, upstream remote) |
test |
bun run test |
Run script tests with coverage |
Run bun start and navigate the menu to access:
| Command | Description |
|---|---|
new-package |
Interactively create a new package |
new-dockerfile |
Generate Dockerfiles with AI or default template |
sync-workflows |
Sync workflows with latest template |
init |
Initialize repo for personal use (remove demo, configure upstream) |
sync-upstream |
Pull updates from upstream embark, preserving fork customizations |
# run all tests with coverage
bun run test
# run a specific test
bun test scripts/__tests__/create-package.test.tsCoverage floor is set in bunfig.toml (coverageThreshold) — that's the
single source of truth, don't repeat the number here. See "Test coverage" in
CLAUDE.md for what the number means: it's checked per file (not the
aggregate), it's driven by the least-covered file, and it's separate from the
77% aggregate target.
Configure secrets at GitHub → Settings → Secrets and variables → Actions.
Required when appDeployment: "gcp".
| Secret | Description |
|---|---|
GCP_PROJECT_ID |
Google Cloud project ID |
GCP_SA_KEY |
Service account JSON (deploy permissions) |
GCP_REGION |
Cloud Run region (e.g. us-central1) |
Required when appDeployment: "netlify".
| Secret | Description |
|---|---|
NETLIFY_TOKEN |
Netlify personal access token |
DOMAIN |
Base domain (e.g. embark.dev) |
Required when appDeployment: "cloudflare-workers".
| Secret | Description |
|---|---|
CF_WORKER_TOKEN |
Cloudflare API token (see permissions below) |
CF_ACCOUNT_ID |
Cloudflare Account ID |
CF_ZONE_ID |
Zone ID of your domain (only if cloudflareUse: true) |
DOMAIN |
Base domain (only if cloudflareUse: true) |
CF_WORKER_TOKEN permissions (My Profile → API Tokens → Create Custom Token):
| Scope | Resource | Permission |
|---|---|---|
| Account | Worker Scripts | Edit |
| Account | Account Settings | Read |
| Zone | DNS | Edit (only if custom domain) |
| Zone | Workers Routes | Edit (only if custom domain) |
Without DNS/Routes permissions, the Worker deploys fine but the custom domain setup fails. See docs/github-secrets.md for details.
Added on top of the Netlify secrets. Required to create/update DNS CNAME records automatically. Not applicable to GCP.
| Secret | Description |
|---|---|
CF_TOKEN |
Cloudflare API token (with DNS edit permissions) |
CF_ZONE_ID |
Zone ID of your domain in Cloudflare |
DOMAIN |
Base domain (e.g. embark.dev) — must match Cloudflare zone |
commit → push to main
→ GitHub Actions detects which packages/ changed
→ Build Docker image (only for changed packages)
→ Push to Artifact Registry
→ Deploy to Cloud Run
Unchanged packages are never rebuilt or redeployed.
Embark is designed to be forked and used as the base for your own monorepo. After forking:
bun install
bun run setupbun run setup will:
- Configure releases (Release Please) or remove release automation
- Protect release files from upstream sync via
.gitattributes - Configure an
upstreamremote pointing toopvibes/embark(push-disabled) - Enable the
merge.ours.driverfor.gitattributesprotection - Install dependencies
- Optionally remove
.gitto start fresh
When embark releases improvements (new scripts, template updates, bug fixes), sync them into your fork:
bun run sync-upstreamThis command:
- Fetches from
upstream - Merges
upstream/mainwithout committing - Automatically removes
packages/embarkandworkflows/embark.ymlif they were re-introduced - Normalizes
apps.jsonc,package.jsonscripts, and README packages table - Commits with
chore(upstream): sync changes from embark@<sha>
You can, but there are caveats:
| Scenario | git pull upstream main |
bun run sync-upstream |
|---|---|---|
merge.ours.driver not configured |
❌ re-introduces demo files | ✅ always works |
| Demo files already in fork history | ✅ protected by .gitattributes |
✅ protected |
| New demo files added in upstream | ✅ removed automatically | |
Normalizes apps.jsonc / package.json |
❌ no | ✅ yes |
Recommendation: always use bun run sync-upstream.
The repo uses Conventional Commits. The following commit types are automatically ignored by commitlint:
Merge branch '...'— local mergesMerge pull request #...— GitHub PR mergesRevert "..."— git revertsv1.2.3— version bump tags
For your own upstream syncs written manually, use: chore: pull updates from embark.
When changes are pushed to main outside of packages/ (scripts, workflows, templates, docs), a release workflow automatically:
- Bumps version — patch increment (e.g., 1.0.0 → 1.0.1)
- Updates
package.json— root monorepo version - Updates README badges — version badge reflects new version
- Creates Git tag — e.g.,
v1.0.1 - Creates GitHub Release — with automatic changelog
Note: Changes inside
packages/do NOT trigger releases. Each package manages its own versioning independently.
| Package | Description |
|---|
The embark website demonstrates Embark's capabilities with an interactive, fully-animated landing page:
Features:
- 🎨 Interactive Terminal — Simulate the entire pre-commit pipeline with keyboard navigation (↑↓ arrows + Enter)
- 🎬 Animated Sections — Scroll-triggered animations using Three.js, GSAP, and ScrollTrigger
- 📱 Responsive Design — Glassmorphism UI with neon accents and dark theme
- 💻 Real Workflow Visualization — Side-by-side dual terminals showing Netlify + Cloud Run deployments
- ⌨️ Keyboard Interactive — Try different deployment paths with full keyboard support
- 🔄 Reset Button — Replay the simulation anytime
Tech Stack:
- Vite + vanilla TypeScript
- Three.js for 3D animations
- GSAP + ScrollTrigger for scroll effects
- Custom CSS with CSS variables
- Responsive and performance-optimized
Running Locally:
bun run --filter @embark/embark dev
bun run --filter @embark/embark build
Made with vibes by @blpsoares