Deploying
A "deploy" takes your project source, builds it into a container image on the platform, and rolls that image out to a slot. You never build locally and you never write a Dockerfile. There are four ways to start a deploy: the CLI, GitHub Actions, the dashboard upload, and a link to a public GitHub repository.
Via the CLI
velixir deploy
This:
- Packages the current directory's source into
.velixir/source.tar.gz. Packing happens in-process, so notarbinary and no build tools are needed on your machine. - Detects your language (informational) and asks the platform for a presigned upload URL, then PUTs the archive straight to object storage.
- Creates a release and finalises it.
- The platform builds the image, enqueues a deployment, and the CLI polls until it lands or fails.
The server-side build is where your dependencies are resolved and your image is assembled, so the first deploy of a project takes a little longer than the ones after it. If the build fails, the failure reason comes back to your terminal and the full build output is on the app's Overview tab.
Deploy to a named slot with velixir deploy --slot staging, or package a different directory with velixir deploy --source-dir ./service.
Make sure it listens, and that we can tell
Your app must listen on 0.0.0.0 at the port in the PORT environment variable. It's 8080 unless you change it under Scaling → Runtime. The health check is off by default, which means a deploy is marked healthy as soon as the container starts, even if nothing is listening. Turn on HTTP health probe in the same place (the default path is /health) and a deploy that never answers fails, while your previous release keeps serving.
Via the dashboard
From an app's Overview tab, click Upload source. Pick a .tar.gz or .zip of your project source: your code plus its manifest, for example package.json, requirements.txt, go.mod, pom.xml, or a .csproj. We sign a one-time URL pointing at object storage; your browser uploads directly to that URL, with no proxy in between, and the build starts as soon as the upload lands.
From a public GitHub repository
On /new, choose Deploy a public GitHub repo and paste its link. velixir downloads the repository itself, works out what it is built with the same way the CLI does, and shows you the commit it is about to deploy. Pick a plan, create the app, and it builds.
- Branches and folders. Paste the link to a branch (
github.com/you/app/tree/dev) to deploy that branch, or to a folder (github.com/you/monorepo/tree/main/apps/web) to deploy just that folder of a monorepo, as the top of the build. - Redeploying. The app remembers the repository, branch and folder. Deploy latest commit on its Deploys tab fetches the newest commit and deploys it. Nothing watches the repository for pushes: to deploy on every push, add the GitHub Actions workflow as well.
- Exactly what was committed. Every file in the repository (or folder) is deployed, with its permissions, including folders like
bin/ordist/that the CLI leaves out of a local folder. Symbolic links are left out. - Limits. Up to 64 MB to download, 256 MB unpacked and 20,000 files. Bigger than that, deploy just the app's folder, or use the CLI.
- A Deploy button for your README. Link to
https://velixir.net/new/github?repo=https://github.com/you/app(the button image ishttps://velixir.net/img/deploy-on-velixir.svg) and anyone can deploy their own copy of your public repository in a few clicks.
Private repositories can't be fetched from a link, and velixir never asks for access to your GitHub account. Deploy them with the CLI from your machine, or with GitHub Actions on every push: both upload the code without velixir reading anything on GitHub. (GitHub answers "not found" for a private repository and a missing one alike, so if a public link says it can't be opened, check it for a typo.)
Via GitHub Actions
A GitHub Actions workflow can deploy every push for you. It installs the velixir CLI on the runner and runs velixir deploy, the same command you run on your own machine. Your source is built on velixir, not on the runner, so the workflow needs no Docker, no language toolchain, and no GitHub App or webhook.
The quickest route is your app's Deploys tab: it has this workflow with the app's ID already filled in, ready to copy, for deploying on every push, on each published release, or only when you run it by hand. The steps below set up the same thing, with the app ID kept in a repository variable instead.
1. Add an API key as a repository secret
Create a key with Write permission under API keys. On GitHub, open your repository's Settings → Secrets and variables → Actions and, on the Secrets tab, add it as VELIXIR_API_KEY.
2. Add your app ID as a repository variable
On the same page, open the Variables tab and add VELIXIR_APP_ID set to your app's ID. velixir apps lists your app IDs, and the app's Deploys tab shows its ID in the velixir deploy --app command. An app ID is not a secret, so a variable is fine.
3. Add the workflow
Save this as .github/workflows/velixir.yml and commit it. If your deploy branch isn't main, change it under branches:.
name: Deploy to velixir
on:
push:
branches: [main]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
# One deploy at a time: a push that lands mid-deploy waits for the one before it.
concurrency:
group: velixir-deploy
cancel-in-progress: false
steps:
- uses: actions/checkout@v7
- name: Install the velixir CLI
shell: bash
run: |
curl -fsSL https://velixir.net/install.sh | sh
echo "$HOME/.velixir/bin" >> "$GITHUB_PATH"
- name: Deploy
env:
VELIXIR_API_KEY: ${{ secrets.VELIXIR_API_KEY }}
VELIXIR_APP_ID: ${{ vars.VELIXIR_APP_ID }}
run: |
if [ -z "$VELIXIR_API_KEY" ] || [ -z "$VELIXIR_APP_ID" ]; then
echo "::error::Add the VELIXIR_API_KEY secret and the VELIXIR_APP_ID variable under Settings > Secrets and variables > Actions."
exit 1
fi
velixir deploy --app "$VELIXIR_APP_ID"
What each step does:
- Checkout puts your repository on the runner.
- Install the velixir CLI downloads the standalone
velixirbinary (no .NET or other runtime needed) and adds it to thePATHfor the next step. - Deploy stops with a clear error if the secret or the variable is missing, then runs
velixir deploy. That uploads your source, waits while velixir builds and rolls it out, and fails the job if the build or the rollout fails.
Deploys run one at a time: a push that lands while a deploy is running waits for it to finish, so an older commit never replaces a newer one.
4. Push and watch it deploy
Push to your deploy branch. The workflow log follows the deploy through to healthy, and the app's Overview tab shows the new release, then the deployment, then the live badge moving to it.
Variations
- Stage first. Add
--slot stagingto thevelixir deployline to deploy without touching live traffic, then swap it live from the Slots tab (staging slots need a Standard plan or above). - Deploy one folder of a monorepo. Add
--source-dir ./serviceto upload just that folder. - Deploy on release, or by hand. Replace the
on:block withrelease:andtypes: [published], or withworkflow_dispatch:for a Run workflow button. The Deploys tab has all three ready to copy. - Don't wait for the rollout.
--no-tailends the job as soon as the deploy is queued. It saves runner minutes, but the job then passes even if the build fails later. - Pin the CLI. The install step always fetches the latest CLI. To pin a version, change it to
curl -fsSL https://velixir.net/install.sh | VELIXIR_VERSION=2.0.5 sh.
Common gotchas
- Missing secret or variable. The Deploy step stops with an error that names what to add. Secrets and variables are on separate tabs under Settings → Secrets and variables → Actions.
- "cannot deploy to it". The API key can see the app but is read-only. Create a key with Write permission and replace the secret.
- "is not an app ID".
VELIXIR_APP_IDmust be the app's ID, the long value that looks like3f2c9a1e-..., not its name or URL. - Branch mismatch. The
branches:filter decides which pushes deploy. Match it to the branch you deploy from. - Build failed. The reason is in the workflow log, and the full build output is on the app's Overview tab. Fix your source and push again.
Slots
Every app has a production slot. Standard plans and above allow more (2 slots in total on Standard, 3 on Pro, up to 10 on Enterprise), so you can deploy a release without touching live traffic:
velixir deploy --slot staging
The staged release answers at staging-<your-app>.velixir.run. When you're happy, open the Slots tab and click Swap slots: the staged release rolls out onto the live slot, with new pods warmed up before they take traffic, and the release that was live moves to the staging slot. Swapping again is your way back. Staging slots run small by default; see Scaling and HA.
Rollback
Every earlier release is listed in the Releases table on the Overview tab. Click Deploy on the one you want and we roll it back out; the edge moves traffic over once it's healthy. Recent Activity on the same tab has a Rollback button for recent deploys, too.
After a deploy
- Set environment variables on the Environment tab - they're injected on the next deploy.
- The Logs tab tails container output live.
- The Metrics strip on the Overview tab updates every 10 seconds with replica count, CPU, memory, and HTTP traffic.