How to structure your repository
The short answer: however you already have it. TalariCloud reads what is there rather than asking you to rearrange it. This page is for when you want to know why it made the choice it made — and for the two layouts where a little structure saves you a lot of configuration.
The three layouts
Nearly every repository is one of these. The platform handles all three, and only the third one asks anything of you.
One application in the root
Nothing to do. Put your manifest where it belongs and the platform finds it — requirements.txt, package.json, composer.json, go.mod, Cargo.toml, Gemfile or mix.exs.
my-app/
├── manage.py
├── requirements.txt
└── myapp/
├── settings.py
└── wsgi.py
One rule worth knowing: the manifest has to be committed. A requirements.txt in .gitignore, or a package-lock.json that never got added, is the most common reason a repository that runs on your laptop is not recognised at all.
A frontend and a backend in one repository
Put each one in its own folder with its own manifest. That is the whole requirement.
my-app/
├── api/
│ ├── requirements.txt
│ └── main.py
└── web/
├── package.json
└── src/
Connect the repository once and the platform reads it, finds both, and proposes a service for each — with the runtime, build command and start command already filled in. They become one application: one plan, one bill, one page in the dashboard. A frontend and a backend are not two subscriptions.
What it will and will not create for you
- At most four services from one repository. Not a technical limit — a deliberate one. A service you have to add yourself is one button; a service invented for you is a monthly charge on a plan you never picked, discovered on the bill.
- Only web services and static sites. A worker and a cron job look identical to a web service from the outside — both are "a program that runs" — so the platform proposes only what it can be certain of, and you change the type afterwards.
- Some folders are skipped on purpose, and each skip is reported with its reason. A React Native app has a
package.jsonand astartscript and looks exactly like a Node service; deployed, it builds, starts, listens on nothing, fails its health check forever, and bills you for it. Shared libraries are skipped for the same reason.
It scans the root and up to fifteen folders, two levels deep. A service in a folder deeper than that is still one click away — point a new service's root directory at it.
Root directory: the one setting that matters
A service's root directory is the folder the build runs in. Set it to api and the build installs api/requirements.txt and starts in api/ — as though that folder were the whole repository.
This is what makes a monorepo work, and it is also the setting people get wrong. Two consequences worth holding in mind:
- Paths in your build and start commands are relative to it, not to the repository root.
- Code outside it is not part of the build. A shared
packages/uithatweb/imports will not be there whenweb/builds alone.
If you share code between services, the honest options are to publish it as a package, or to set the root directory to the repository root and let the build command reach into the subfolder:
# root directory: . (the whole repository)
npm ci && npm run build --workspace web
Declaring it yourself: talaricloud.yaml
Discovery is a guess, however good. A file is a decision — and a decision is worth writing down once your repository holds more than a handful of services, or when the next person to deploy it should not have to reconstruct what you clicked.
Put talaricloud.yaml in the repository root:
services:
- name: beamline-api
type: web
runtime: python
rootDir: api
buildCommand: pip install -r requirements.txt
startCommand: gunicorn app.wsgi:application --bind 0.0.0.0:$PORT
preDeployCommand: python manage.py migrate --noinput
healthCheckPath: /healthz
envVars:
- key: DJANGO_SETTINGS_MODULE
value: app.settings.production
- key: SECRET_KEY
generateValue: true
- name: beamline-web
type: static
rootDir: web
buildCommand: npm ci && npm run build
staticPublishPath: dist
- name: beamline-worker
type: worker
runtime: python
rootDir: api
startCommand: celery -A app worker -l info
databases:
- name: beamline-db
A blueprint overrides the four-service cap entirely. A file declaring twelve services gets twelve, because somebody wrote them down — which is the whole difference between a declaration and a guess.
generateValue: true asks the platform to invent a secret and keep it, so a SECRET_KEY nobody should have to think of is never one somebody has to commit.
Coming from Render? A render.yaml is read as-is. Nothing to rename, nothing to rewrite — the keys above are Render's keys.
One address for a frontend and a backend
Two services normally mean two addresses, and a browser calling one from the other means CORS, cookie domains, and an afternoon you will not get back.
Instead, route a path on one service's address to another: /api on your frontend's address reaches your backend, and the browser only ever sees one origin. No CORS to configure, cookies simply work, and there is one address to share.
myapp.talaricloud.com/ → web (your frontend)
myapp.talaricloud.com/api/* → api (your backend)
This is set on the frontend service, after both are deployed. Structurally it means one useful thing for your repository: your frontend can call /api/... with a relative path, in development and in production, and never needs to know a hostname at all.
Things that quietly cost you a deploy
- The manifest is gitignored. If it is not committed, it does not exist as far as the platform is concerned.
- The start command binds a fixed port. It must use
$PORTand bind0.0.0.0. Binding127.0.0.1or a hard-coded8000means nothing outside the container can reach it, and the health check fails on an app that looks perfectly healthy in the logs. - Secrets committed to the repository. Use environment variables; they are encrypted at rest and masked until you ask. A key in git history is a key you have to rotate, not delete.
- A build that needs a file outside the root directory. See above — it will not be there.
- Two services sharing a folder and fighting over it. An API and a worker should share a root directory — same code, different start command. That is correct and supported. Two different applications in one folder is not.
A checklist
- Every deployable app has its own folder and its own committed manifest.
- The start command binds
0.0.0.0:$PORT. - No secrets in the repository.
- Migrations are a pre-deploy command, not something your app does on startup.
- More than four services, or you want the layout written down? Add
talaricloud.yaml.
Deploy it
Connect the repository and let the platform read it. Nothing to rearrange first.