1---
2title: Getting Started
3description: A complete quickstart for a Git host protected by a shared credential. Deploy it to Cloudflare, push a repository, and clone it back.
4---
5
6import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
7
8For a step-by-step explanation, start with [Push your first repository](/git/tutorial/part-1).
9This page provides a complete host protected by one shared credential.
10
11You need a Cloudflare account with Workers, Durable Objects, and R2
12enabled. [Cloudflare setup](/cloudflare/setup) connects one.
13
14## Install
15
16<Tabs syncKey="pkgManager">
17 <TabItem label="bun" icon="bun">
18 <Code code="bun add alchemy" lang="sh" />
19 </TabItem>
20 <TabItem label="npm" icon="npm">
21 <Code code="npm install alchemy" lang="sh" />
22 </TabItem>
23 <TabItem label="pnpm" icon="pnpm">
24 <Code code="pnpm add alchemy" lang="sh" />
25 </TabItem>
26 <TabItem label="yarn" icon="seti:yarn">
27 <Code code="yarn add alchemy" lang="sh" />
28 </TabItem>
29</Tabs>
30
31## Define the host
32
33```typescript
34// src/git.ts
35import * as Alchemy from "alchemy";
36import * as Cloudflare from "alchemy/Cloudflare";
37import * as Git from "alchemy/Git";
38import * as Effect from "effect/Effect";
39import * as Layer from "effect/Layer";
40import * as HttpRouter from "effect/http/HttpRouter";
41import * as Http from "alchemy/Http";
42import * as Redacted from "effect/Redacted";
43import * as HttpServerResponse from "effect/http/HttpServerResponse";
44import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
45import * as HttpApiSecurity from "effect/http-api/HttpApiSecurity";
46
47export const GitObjects = Cloudflare.R2.Bucket("GitObjects");
48export const GitSecret = Effect.gen(function* () {
49 const Random = yield* Alchemy.Random;
50 return yield* Random("GitSecret");
51});
52
53/** One shared secret, sent as the password of HTTP Basic. */
54export const Authentication = HttpRouter.middleware(
55 Effect.gen(function* () {
56 const secret = yield* (yield* GitSecret).text;
57 return (httpEffect) =>
58 Effect.gen(function* () {
59 const { password } = yield* HttpApiBuilder.securityDecode(HttpApiSecurity.basic);
60 if (Redacted.value(password) === Redacted.value(yield* secret)) {
61 return yield* httpEffect;
62 }
63 return HttpServerResponse.empty({
64 status: 401,
65 headers: { "www-authenticate": 'Basic realm="git"' },
66 });
67 }).pipe(Effect.provide(Alchemy.RuntimeContext.phantom));
68 }),
69);
70
71const GitLive = Layer.mergeAll(
72 Git.ApiLive.pipe(Layer.provide(Authentication.layer)),
73 Git.InternalApiLive,
74).pipe(
75 Layer.provide(Git.ApiHandlersLive),
76 Layer.provide(Git.ReposDurableObject),
77 Layer.provide(Git.RegistryDurableObject),
78 Layer.provide(Git.HasherInline),
79 Layer.provide(Git.BlobStoreR2(GitObjects)),
80 Layer.provide(Http.Platform),
81);
82
83export default class GitHost extends Cloudflare.Worker<GitHost>()(
84 "Git",
85 { main: import.meta.url, ...Git.GIT_WORKER_OPTIONS },
86 Effect.gen(function* () {
87 const fetch = yield* HttpRouter.toHttpEffect(GitLive);
88 return { fetch };
89 }),
90) {}
91```
92
93`Git.ApiLive` registers the public Git routes on the application's router.
94`Git.ApiHandlersLive` supplies their shared implementation. `Git.InternalApiLive`
95registers the internal hash route outside application authentication.
96Your application chooses its server, middleware, platform, and CORS policy.
97
98The engine holds no users and no credentials. Who may call a route is
99decided by the middleware applied to its route layer, and `Git.ApiLive`
100registers every git route: the REST plane, the git wire, the raw reads,
101and the GitHub facade. The smallest thing that secures a fresh host is
102one shared secret. A request that presents it may do anything; any
103other request is refused with a `401` that makes `git` ask for a
104password. The secret is an `Alchemy.Random`, minted on the first deploy
105and stable after it. `GIT_WORKER_OPTIONS` sets the compatibility flags
106and the CPU limit a push needs.
107
108Add the host to your stack. The `Random` is a resource like any other,
109so the stack can output its value:
110
111```typescript
112// alchemy.run.ts
113import * as Alchemy from "alchemy";
114import * as Cloudflare from "alchemy/Cloudflare";
115import * as Output from "alchemy/Output";
116import * as Effect from "effect/Effect";
117import * as Redacted from "effect/Redacted";
118import GitHost, { GitSecret } from "./src/git.ts";
119
120export default Alchemy.Stack(
121 "GitService",
122 { providers: Cloudflare.providers(), state: Cloudflare.state() },
123 Effect.gen(function* () {
124 const git = yield* GitHost;
125 const secret = yield* GitSecret;
126 return {
127 url: git.url.as<string>(),
128 secret: Output.map(secret.text, Redacted.value),
129 };
130 }),
131);
132```
133
134## Deploy
135
136<Tabs syncKey="pkgManager">
137 <TabItem label="bun" icon="bun">
138 <Code code="bun alchemy deploy" lang="sh" />
139 </TabItem>
140 <TabItem label="npm" icon="npm">
141 <Code code="npm run alchemy deploy" lang="sh" />
142 </TabItem>
143 <TabItem label="pnpm" icon="pnpm">
144 <Code code="pnpm alchemy deploy" lang="sh" />
145 </TabItem>
146 <TabItem label="yarn" icon="seti:yarn">
147 <Code code="yarn alchemy deploy" lang="sh" />
148 </TabItem>
149</Tabs>
150
151The stack prints the Worker's URL and the secret. The rest of this page
152calls them `$HOST` and `$GIT_SECRET`:
153
154```sh
155export HOST=...
156export GIT_SECRET=...
157```
158
159## Create a repository
160
161Repositories are named `owner/name`. The owner is a name you choose;
162with one shared secret there are no users behind it:
163
164```sh
165curl -u "x:$GIT_SECRET" -X POST "$HOST/api/v1/repos" \
166 -H "Content-Type: application/json" \
167 -d '{"owner":"acme","name":"web"}'
168```
169
170## Push
171
172Use the full `$HOST` URL from the deployment output. When Git prompts, enter
173`x` as the username and `$GIT_SECRET` as the password:
174
175```sh
176git remote add origin "$HOST/acme/web.git"
177git -c credential.helper= push -u origin main
178```
179
180Clone it back to prove the round trip:
181
182```sh
183git -c credential.helper= clone "$HOST/acme/web.git" verify
184git -C verify fsck --strict
185```
186
187## Continue with the tutorial
188
189The tutorial introduces one behavior per part. Each part includes a deploy and
190commands that verify what changed:
191
1921. [Push your first repository](/git/tutorial/part-1) — deploy Git, push a commit,
193 and clone it back.
1942. [Control access](/git/tutorial/part-2) — require a shared credential and verify
195 anonymous requests fail.
1963. [Publish a repository](/git/tutorial/part-3) — allow anonymous clones while
197 keeping writes protected.
1984. [Give users their own credentials](/git/tutorial/part-4) — use accounts and
199 individual API keys.
2005. [Add your application's API](/git/tutorial/part-5) — serve `/me` beside Git
201 with the same authenticated user.
2026. [Protect a branch](/git/tutorial/part-6) — reject deletion of `main` with a
203 policy in your HTTP handler.
204
205[Building blocks](/git/blocks) is the reference for each line of
206`GitLive`, and [Recipes](/git/recipes) covers S3 bytes, Lambda hashing,
207and how the host scales.