1---
2title: "Part 1: Push your first repository"
3description: Deploy a Git host, push a commit, and clone it back. Add storage and hashing one layer at a time.
4---
5
6import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
7
8Start with a Git remote you can push to. This part deploys a Worker backed by
9Durable Objects and R2, then sends a real commit through it and clones it back.
10
11:::caution
12This first deployment is open: anyone who knows its URL can create repositories,
13read them, and write to them. Use a disposable repository. [Part 2](/git/tutorial/part-2)
14adds access control before you use the host for private work.
15:::
16
17## Create the project
18
19You need Bun or Node.js 22+, Git, and a Cloudflare account with Workers, Durable Objects, and R2.
20Complete [Cloudflare setup](/cloudflare/setup) to connect your account.
21
22<Tabs syncKey="pkgManager">
23 <TabItem label="bun" icon="bun">
24 <Code code={`mkdir git-tutorial
25cd git-tutorial
26bun init -y
27bun add alchemy effect
28mkdir src`} lang="sh" />
29 </TabItem>
30 <TabItem label="npm" icon="npm">
31 <Code code={`mkdir git-tutorial
32cd git-tutorial
33npm init -y
34npm install alchemy effect
35mkdir src`} lang="sh" />
36 </TabItem>
37 <TabItem label="pnpm" icon="pnpm">
38 <Code code={`mkdir git-tutorial
39cd git-tutorial
40pnpm init
41pnpm add alchemy effect
42mkdir src`} lang="sh" />
43 </TabItem>
44 <TabItem label="yarn" icon="seti:yarn">
45 <Code code={`mkdir git-tutorial
46cd git-tutorial
47yarn init -y
48yarn add alchemy effect
49mkdir src`} lang="sh" />
50 </TabItem>
51</Tabs>
52
53Run the remaining commands from this project directory. Keep the same directory,
54stack name, and deployment stage throughout the tutorial so each deploy updates
55this host.
56
57## Add the Git routes
58
59Create `src/git.ts`:
60
61```typescript
62// src/git.ts
63import * as Git from "alchemy/Git";
64import * as Layer from "effect/Layer";
65
66export const GitLive = Git.ApiLive.pipe(
67 Layer.provide(Git.ApiHandlersLive),
68);
69```
70
71`Git.ApiLive` registers the Git HTTP endpoints, including the protocol used by
72`git push` and `git clone`. `Git.ApiHandlersLive` implements those endpoints.
73Its remaining dependencies describe where repositories live and how incoming
74objects are checked. Supply each one below before deploying.
75
76## Store repository data
77
78```diff lang="typescript"
79// src/git.ts
80export const GitLive = Git.ApiLive.pipe(
81 Layer.provide(Git.ApiHandlersLive),
82+ Layer.provide(Git.ReposDurableObject),
83);
84```
85
86`Git.ReposDurableObject` gives each repository a Durable Object. It stores the
87repository's refs and object metadata and coordinates writes to that repository.
88
89## Look up repositories by name
90
91```diff lang="typescript"
92// src/git.ts
93 Layer.provide(Git.ReposDurableObject),
94+ Layer.provide(Git.RegistryDurableObject),
95);
96```
97
98The registry maps an `owner/name`, such as `acme/web`, to its repository. This
99lets an incoming request find the right Durable Object.
100
101## Verify incoming Git objects
102
103```diff lang="typescript"
104// src/git.ts
105 Layer.provide(Git.RegistryDurableObject),
106+ Layer.provide(Git.HasherInline),
107);
108```
109
110Git objects are identified by hashes of their contents. `Git.HasherInline`
111verifies incoming objects in the Worker during a push.
112
113## Store packs and large objects
114
115```diff lang="typescript"
116// src/git.ts
117+import * as Cloudflare from "alchemy/Cloudflare";
118import * as Git from "alchemy/Git";
119import * as Layer from "effect/Layer";
120
121+const GitObjects = Cloudflare.R2.Bucket("GitObjects", { forceDestroy: true });
122+
123export const GitLive = Git.ApiLive.pipe(
124```
125
126This bucket holds packs, clone bundles, and large objects. `forceDestroy: true`
127lets you remove the tutorial stack even when the bucket contains data; destroying
128this stack also deletes those objects.
129
130Connect the bucket to Git:
131
132```diff lang="typescript"
133// src/git.ts
134 Layer.provide(Git.HasherInline),
135+ Layer.provide(Git.BlobStoreR2(GitObjects)),
136);
137```
138
139The R2 layer supplies the blob-storage operations used by the Worker and the
140repository Durable Objects.
141
142## Serve Git from a Worker
143
144Create `src/host.ts`:
145
146```typescript
147// src/host.ts
148import * as Cloudflare from "alchemy/Cloudflare";
149import * as Git from "alchemy/Git";
150import * as Http from "alchemy/Http";
151import * as Effect from "effect/Effect";
152import * as Layer from "effect/Layer";
153import * as HttpRouter from "effect/http/HttpRouter";
154import { GitLive } from "./git.ts";
155
156export default class GitHost extends Cloudflare.Worker<GitHost>()(
157 "GitHost",
158 { main: import.meta.url, ...Git.GIT_WORKER_OPTIONS },
159 Effect.gen(function* () {
160 const fetch = yield* HttpRouter.toHttpEffect(
161 GitLive.pipe(Layer.provide(Http.Platform)),
162 );
163 return { fetch };
164 }),
165) {}
166```
167
168`HttpRouter.toHttpEffect` turns the route layer into the Worker's `fetch` handler.
169`Http.Platform` provides its HTTP platform dependencies. `GIT_WORKER_OPTIONS`
170sets the Worker compatibility flags and CPU limit used by Git.
171
172## Declare the stack
173
174Create `alchemy.run.ts`:
175
176```typescript
177// alchemy.run.ts
178import * as Alchemy from "alchemy";
179import * as Cloudflare from "alchemy/Cloudflare";
180import * as Effect from "effect/Effect";
181import GitHost from "./src/host.ts";
182
183export default Alchemy.Stack(
184 "GitTutorial",
185 { providers: Cloudflare.providers(), state: Cloudflare.state() },
186 Effect.gen(function* () {
187 const host = yield* GitHost;
188 return { url: host.url.as<string>() };
189 }),
190);
191```
192
193Yielding the Worker includes its bucket and Durable Object bindings in the
194stack. The output gives you the URL clients will use.
195
196## Deploy
197
198<Tabs syncKey="pkgManager">
199 <TabItem label="bun" icon="bun">
200 <Code code={`bun alchemy deploy`} lang="sh" />
201 </TabItem>
202 <TabItem label="npm" icon="npm">
203 <Code code={`npx alchemy deploy`} lang="sh" />
204 </TabItem>
205 <TabItem label="pnpm" icon="pnpm">
206 <Code code={`pnpm alchemy deploy`} lang="sh" />
207 </TabItem>
208 <TabItem label="yarn" icon="seti:yarn">
209 <Code code={`yarn alchemy deploy`} lang="sh" />
210 </TabItem>
211</Tabs>
212
213Copy the printed URL, including `https://`, without a trailing slash:
214
215```sh
216export HOST="https://your-worker.workers.dev"
217```
218
219Verify that the Git API is reachable:
220
221```sh
222curl --fail-with-body "$HOST/api/v1/repos"
223```
224
225It returns a JSON repository list. On a new host the list is empty. A new
226`workers.dev` deployment can briefly return a Cloudflare 404 while it propagates;
227repeat this read check until the API responds before creating the repository.
228Updates in later parts can also briefly serve the previous version.
229
230## Create a repository
231
232```sh
233curl --fail-with-body -X POST "$HOST/api/v1/repos" \
234 -H "Content-Type: application/json" \
235 -d '{"owner":"acme","name":"web"}'
236```
237
238The repository is named `acme/web`. At this stage `acme` is just a namespace;
239there are no user accounts. Creating a repository reserves that name before you
240push any commits.
241
242## Push a commit
243
244```sh
245git init -b main work
246git -C work config user.name "Tutorial"
247git -C work config user.email "tutorial@example.com"
248printf '# My repository\n' > work/README.md
249git -C work add README.md
250git -C work commit -m "First commit"
251git -C work remote add origin "$HOST/acme/web.git"
252git -C work push -u origin main
253```
254
255The push uploads the commit and its objects, then creates `main` on your host.
256
257## Clone it back
258
259```sh
260git clone "$HOST/acme/web.git" verify
261git -C verify fsck --strict
262git -C work rev-parse HEAD
263git -C verify rev-parse HEAD
264```
265
266The two commit IDs should match, and `fsck` should report no corrupt or missing
267objects. You now have a working Git remote.
268
269Continue to [Part 2: Control access](/git/tutorial/part-2). If you stop here,
270remove the open learning deployment:
271
272<Tabs syncKey="pkgManager">
273 <TabItem label="bun" icon="bun">
274 <Code code={`bun alchemy destroy`} lang="sh" />
275 </TabItem>
276 <TabItem label="npm" icon="npm">
277 <Code code={`npx alchemy destroy`} lang="sh" />
278 </TabItem>
279 <TabItem label="pnpm" icon="pnpm">
280 <Code code={`pnpm alchemy destroy`} lang="sh" />
281 </TabItem>
282 <TabItem label="yarn" icon="seti:yarn">
283 <Code code={`yarn alchemy destroy`} lang="sh" />
284 </TabItem>
285</Tabs>