Deploy your first application
From an empty portal to a running application on your own hostname, in the order that works.
This walks through a ZIP upload, which needs nothing connected anywhere. Deploying from Git is two more steps and is what you will use afterwards.
1. Create a project
A project is a name and a slug. It comes with one environment, production. Environments
exist so the same application can have a staging copy without a second project.
2. Add an application
An application belongs to an environment and lives on one server. You name it and tell it which port your container listens on — that is the one value the platform cannot work out for itself, and it is what the health gate connects to and what the router forwards to.
Optional at creation, changeable later:
| Setting | Default | What it does |
|---|---|---|
| Exposed service name | Detected | Which service in a multi-service compose file is the one facing the world. |
| Compose path / Dockerfile path | Detected | Where to look, if the file is not at the source root. |
| Context path | Source root | The build context. |
| Health timeout | 60 seconds | How long the health gate waits before failing the deployment. |
| CPU limit | None | Written into the rendered compose file as cpus. |
| Memory limit | None | Written as mem_limit. |
| Image retention | 3 | How many previous images to keep. Each one is what a rollback re-applies. |
| Deploy strategy | RECREATE | See deployments and rollback. |
3. Upload a ZIP
Zip your source and upload it. The platform looks for compose.yaml, compose.yml,
docker-compose.yaml, or docker-compose.yml first, then a Dockerfile, and — if
Nixpacks is enabled and the tree carries a recognised
manifest — builds it with Nixpacks. If none of those apply it fails with a message naming
both options.
The upload cap is 200 MB, and the archive is refused whole rather than partially extracted if it exceeds that, 20,000 entries, or 2 GB extracted. Any entry whose resolved path escapes the destination rejects the entire archive.
4. Watch the build
The build streams live. Every line of docker compose build output reaches the portal as it
happens, and lands in a size-capped log file on the server. Values of your environment
variables are scrubbed out at the single point every line passes through.
When the build finishes, the platform brings the containers up and health-gates them: your
container's own HEALTHCHECK if it declares one, otherwise a TCP connect to the port you
named, retried with backoff until the health timeout.
Until you attach a hostname, the application is published on a numbered port on the server — shown on the application's page — so you can reach it immediately.
5. Put it on a domain
This order is not ceremony, and doing it out of order costs you an hour.
Add the hostname on the application. The portal shows the exact A
record to create.
Create that record at your DNS provider, and wait for it to propagate.
Press Check DNS. Only once the hostname resolves to this server does a router get attached and a certificate get requested.
Why the order matters
Attaching a router before DNS resolves makes Traefik ask Let's Encrypt for a
certificate it cannot validate. Production allows five such failures per
hostname per hour — get it wrong and you are locked out of your own
certificate for an hour. Develop against PROXY_ACME_STAGING=true, whose
limits are far looser and whose certificates browsers do not trust.
Once a hostname is attached, the application stops being published to the internet on a numbered port. It keeps a loopback-only binding on the server so the platform can still health-check it.
What you have now
A running application, on your own domain, with a certificate that renews itself. From here: