# FAQ (/docs/faq) ## How should I handle database connections on an app deployed to Phemeral? [#how-should-i-handle-database-connections-on-an-app-deployed-to-phemeral] Database connections should not be expected to persist across invocations. Phemeral environments may recycle the underlying process between requests, which means idle connections in a pool can become stale or closed. ### Use your provider's connection pooler if available [#use-your-providers-connection-pooler-if-available] If your database provider already offers a connection pooler, you don't need to manage connection pooling in your application. For example, Phemeral's managed Postgres offering provides a **pooled connection URI** by default, so your app code can connect directly without configuring its own pool. A **direct connection URI** is also available when you need to bypass the pooler. Check your provider's documentation to see if a managed pooler is available. If it is, prefer that over application-level pooling. ### Application-level pooling [#application-level-pooling] If you do manage your own connection pool, configure it to validate or recycle connections proactively. ### SQLAlchemy [#sqlalchemy] Set `pool_pre_ping` to validate and re-establish connections before executing a query: ```python from sqlalchemy import create_engine engine = create_engine( "postgresql+psycopg2://user:pass@host/db", pool_pre_ping=True, ) ``` This issues a lightweight check (e.g. `SELECT 1`) on each connection checkout, automatically replacing connections that the server has closed. ### asyncpg [#asyncpg] Set `max_inactive_connection_lifetime` to a low value (such as `1` second) so that idle connections in the pool are recycled frequently: ```python import asyncpg; pool = await asyncpg.create_pool( "postgresql://user:pass@host/db", max_inactive_connection_lifetime=1, ) ``` This ensures connections are not held long enough to go stale across invocations. # Quickstart (/docs) Phemeral deploys your Python backends automatically. Connect a GitHub repository, push your code, and get a live URL. If you don't already have a project, use our [hello-world repository](https://github.com/phemeral-dev/hello-world) to get started! ## 1. Create an Account [#1-create-an-account] Sign up at [phemeral.dev](https://phemeral.dev/pricing). When your account is created, Phemeral automatically provisions an organization with a default project called **Launchpad** and three default environments: **Production**, **Staging**, and **Development**. ## 2. Connect Your GitHub Account [#2-connect-your-github-account] 1. Navigate to [User Settings](/dashboard/user-settings). 2. Click **Connect** in the GitHub section. 3. Authorize the Phemeral GitHub App on your GitHub account and grant it access to the repositories you want to deploy. ## 3. Connect a Repository to Your Project [#3-connect-a-repository-to-your-project] 1. Open your project and go to the **Settings** tab. 2. Select the repository you want to deploy from the list of accessible repositories. 3. Map a branch (e.g. `main`) to an environment (e.g. **Production**). ## 4. Deploy [#4-deploy] Push a commit to your mapped branch. Phemeral automatically: * Detects your Python framework, package manager, and project structure * Builds and deploys your application * Assigns a live URL on `.phemeral.app` View the build status and logs from the deployment page in your dashboard. ## Next Steps [#next-steps] * [Connect GitHub](/docs/getting-started/connect-github): Detailed walkthrough of the GitHub setup * [Your First Deployment](/docs/getting-started/first-deployment): Step-by-step tutorial for deploying a project * [Concepts](/docs/concepts): Learn how Projects, Environments, and Deployments work together # Database Clusters (/docs/concepts/database-clusters) A **database cluster** is a fully managed Postgres database that belongs to one of your projects. Phemeral provisions the database, keeps it patched, and scales its compute up and down with your traffic. Managed Postgres is available on the **Pro** plan. ## What a Database Cluster Contains [#what-a-database-cluster-contains] Each cluster is an independent Postgres database with: * A **database** and a **role** that owns it. * A **pooled connection URI** your application uses to connect (recommended for most uses), plus a **direct connection URI** available when you need it. * Its own compute, which scales independently of every other cluster. ## Clusters and Projects [#clusters-and-projects] Database clusters are created inside a project, alongside that project's environments and deployments. A project can hold **up to 20 database clusters**. Because clusters are associated to a project, they are deployed geographically close to that project's compute, reducing latency between your application and its database. ## Automatic Scaling [#automatic-scaling] Clusters scale their compute automatically as load changes. * Compute scales between **0.25 and 8 vCPU**, with **1 GB to 32 GB of RAM**, rising and falling with your traffic. * When a cluster receives **no traffic for 5 minutes**, it **scales to zero**. While a cluster is scaled to zero, it incurs **no compute charge**. * A cluster that has scaled to zero **scales back up within a few hundred milliseconds** the next time it is accessed, so the first query after an idle period transparently wakes it. ## Connection Pooling [#connection-pooling] Every cluster is fronted by **a connection pooler**. A single cluster supports up to **10,000 concurrent connections** through the pooler. The pooled connection URI is the default and is what you should use for most applications. A **direct connection URI** is also available for cases where you need to bypass the pooler, such as administrative tools or long-running sessions. See the [FAQ](/docs/faq) for guidance on configuring your application's database client for an environment where processes may be recycled between requests. ## Extensions [#extensions] Support for Postgres extensions is **coming soon**. Today, clusters run a standard Postgres 18 configuration. ## More [#more] For step-by-step instructions, see [Create and Connect to a Database Cluster](/docs/guides/managed-postgres). For more, see the [Managed Postgres reference](/docs/reference/managed-postgres). # Deployments (/docs/concepts/deployments) A **deployment** is a specific version of your code running inside an environment. Each time you push to a mapped branch or trigger a deploy, Phemeral creates a new deployment. ## Deployment Lifecycle [#deployment-lifecycle] Every deployment moves through a series of stages: 1. **Building:** Phemeral analyzes your project, installs dependencies, and builds the project. 2. **Successful:** The build completed and the deployment is ready to serve traffic. 3. **Failed:** The build encountered an error. Check the build logs for details. A deployment's status is one of: `building`, `successful`, or `failed`. ## What Happens During a Deployment [#what-happens-during-a-deployment] When a deployment is created, Phemeral: 1. **Detects your project settings:** Python version, framework (FastAPI, Flask, or Django), package manager (uv, poetry, or pip), project root directory, and application entry point. See [Project Structure Requirements](/docs/reference/project-structure). 2. **Chooses a runtime command:** Phemeral uses the project's custom start command if one is saved. Otherwise, it uses the autodetected default command for the framework. 3. **Builds your project and assigns compute** 4. **Assigns a domain:** Every deployment receives an auto-generated URL in the format `{deployment-id}.phemeral.app`. ## Runtime Command Resolution [#runtime-command-resolution] Phemeral still autodetects your framework, project root, and application entry point even when you use a custom start command. That autodetection determines the default runtime behavior. A project-level custom start command only changes the command Phemeral starts for **new** deployments. If you clear the custom command later, future deployments fall back to the autodetected default. Existing deployments are not modified. ## Build Logs [#build-logs] Each stage of the build is logged with a timestamp. You can view build logs on the deployment detail page in the dashboard. ## Domains [#domains] Deployments have two types of domains: * **Deployment domain:** Auto-generated for every deployment: `{deployment-id}.phemeral.app`. This always points to the specific deployment, regardless of which deployment is current on the environment. * **Environment domains:** Custom subdomains assigned to the environment (e.g. `my-api-production.phemeral.app`). These point to whichever deployment is currently active on the environment. When a new deployment is set as the current deployment on its environment, the environment's custom domains begin routing traffic to the new deployment. ## Runtime [#runtime] Deployments run on optimized compute managed by Phemeral's compute orchestration layer. Compute resources scale to zero when traffic is idle, and scale up (almost) instantly when requests arrive (\<75ms). ## Setting the Current Deployment [#setting-the-current-deployment] When a deployment triggered by a GitHub push completes successfully, it is automatically set as the current deployment on its environment. The environment's custom domains immediately begin routing traffic to the new deployment. ## Runtime Logs [#runtime-logs] In addition to build logs, you can view runtime logs for a deployment. These are the logs your application produces while handling requests. Runtime logs are available on the deployment detail page and support pagination. # Environments (/docs/concepts/environments) An **environment** is a deployment target within a project. Environments let you run different versions of your code side by side, such as a production environment serving live traffic and a staging environment for testing. ## What an Environment Contains [#what-an-environment-contains] Each environment has: * **A current deployment:** The deployment that currently receives live traffic for this environment. When a new deployment succeeds, it can be set as the current deployment. * **Deployment history:** A record of all deployments created for this environment. * **Environment variables:** Key-value pairs that are injected into your application at build time. Values are encrypted at rest. See [Manage Environment Variables](/docs/guides/environment-variables). * **Custom domains:** One or more subdomains on `.phemeral.app` that route traffic to the environment's current deployment. See [Configure Custom Domains](/docs/guides/custom-domains). * **GitHub branch mapping:** An optional link between a GitHub branch and this environment. Pushes to the mapped branch trigger automatic deployments. See [Set Up GitHub Continuous Deployment](/docs/guides/github-integration). * **Scheduled webhooks:** Optional scheduled HTTP requests that target the environment's current deployment through its active domain. See [Configure Scheduled Webhooks](/docs/guides/scheduled-webhooks). ## Creating an Environment [#creating-an-environment] When you create a project, Phemeral automatically creates three environments for you: **Production**, **Staging**, and **Development**. You can create additional environments from the project's **Environments** page. When creating an environment, you provide: * An **environment name**. * Optionally, **initial environment variables**. Project creation provides separate variable groups for **Production**, **Staging**, and **Development**. ## How Traffic Reaches an Environment [#how-traffic-reaches-an-environment] Each environment can be assigned one or more **custom domains** in the format `{subdomain}.phemeral.app`. When a request arrives at a custom domain, it is routed to the environment's **current deployment**. Individual deployments also have their own auto-generated domains in the format `{deployment-id}.phemeral.app`. These are useful for previewing a specific deployment regardless of which deployment is currently active on the environment. ## Environments and Branches [#environments-and-branches] An environment can have a GitHub branch mapped to it. When code is pushed to that branch, Phemeral automatically: 1. Downloads the code from the repository. 2. Creates a new deployment in the mapped environment. 3. Sets the new deployment as the environment's current deployment. Multiple environments can map to different branches of the same repository. For example: | Environment | Branch | | ----------- | ---------------------- | | Production | `main` | | Staging | `pre-release` | | Development | `fancy-feature-branch` | Unmapping a branch stops automatic deployments for that environment. Existing deployments are not affected. ## Scheduled Webhooks [#scheduled-webhooks] Scheduled webhooks are environment-bound HTTP requests that Phemeral runs on a UTC cron schedule. Although you configure them from the project's **Settings** tab, each scheduled webhook targets one environment. Phemeral sends the request to that environment's oldest active domain, which means the request always reaches the environment's **current deployment**. This makes scheduled webhooks useful for tasks that should follow an environment over time, such as: * warming caches after traffic goes idle * calling internal maintenance endpoints * triggering application-level jobs on a schedule If an environment has no active domains, scheduled webhooks for that environment cannot run until a domain becomes active again. # Platform Overview (/docs/concepts) Phemeral is a deployment platform for Python backends. It takes your source code, automatically detects your framework and dependencies, builds your application, and runs it on a lightweight virtual machine with a public URL. ## How Phemeral Works [#how-phemeral-works] When you push code to a connected GitHub repository, Phemeral: 1. Downloads the source code from your repository. 2. Analyzes the project to detect the Python framework (FastAPI, Flask, or Django), package manager (uv, poetry, or pip), and application entry point. 3. Builds your project 4. Deploys the built project to the Phemeral cloud 5. Assigns a public URL on `.phemeral.app` and serves traffic to your application. ## Core Concepts [#core-concepts] Phemeral is organized around three main concepts: * **[Projects](/docs/concepts/projects):** A project groups related environments together, connects to a single GitHub repository, and can define a project-level start command for new deployments. * **[Environments](/docs/concepts/environments):** An environment is a deployment target within a project (e.g. production, staging). Each environment has its own configuration, environment variables, and custom domains. * **[Deployments](/docs/concepts/deployments):** A deployment is a specific version of your code running in an environment. Environments track a current deployment that receives live traffic. ### How They Relate [#how-they-relate] ``` Organization └── Project (connected to a GitHub repository) ├── Environment: "Production" (branch: main) │ ├── Current Deployment → receives traffic │ └── Previous Deployments (history) ├── Environment: "Staging" (branch: pre-release) │ ├── Current Deployment → receives traffic │ └── Previous Deployments (history) └── Environment: "Development" (branch: fancy-feature-branch) ├── Current Deployment → receives traffic └── Previous Deployments (history) ``` An **organization** is the top-level container. It holds your team members, billing plan, and projects. When you create an account, an organization is created for you with a default project called **Launchpad** and three default environments: **Production**, **Staging**, and **Development**. ## Supported Stack [#supported-stack] Phemeral currently supports Python backends using: | Category | Supported Options | | ---------------- | ------------------------------------------------------------------------------- | | Frameworks | FastAPI, Flask, Django | | Package managers | uv, poetry, pip (requirements.txt), or any pyproject.toml based package manager | | Python versions | Specified via `.python-version` file (defaults to 3.12 if unspecified) | See [Supported Frameworks](/docs/reference/supported-frameworks) for details on how autodetection works. # Projects (/docs/concepts/projects) A **project** is a container that groups related environments together. Each project can be connected to a single GitHub repository for continuous deployment. ## What a Project Contains [#what-a-project-contains] A project holds: * **Environments:** One or more deployment targets (e.g. production, staging, preview). See [Environments](/docs/concepts/environments). * **GitHub repository connection:** An optional link to a GitHub repository. When connected, pushes to mapped branches trigger automatic deployments. * **Deployment startup configuration:** An optional custom start command that overrides Phemeral's autodetected runtime command for new deployments in the project. * **Deployment history:** All deployments created across the project's environments. ## Creating a Project [#creating-a-project] When you create a new project, you provide: * A **project name**. * Optionally, **initial environment variables** for the **Production**, **Staging**, and **Development** environments. Every new project starts with three environments: **Production**, **Staging**, and **Development**. Every organization starts with a default project called **Launchpad** that includes those same three environments. You can create additional projects from the dashboard by navigating to **Dashboard → New Project**. ## Connecting a GitHub Repository [#connecting-a-github-repository] A project can be connected to one GitHub repository at a time. This enables continuous deployment: when you push to a branch that is mapped to an environment, Phemeral automatically creates a new deployment. To connect a repository: 1. Ensure your GitHub account is connected in [User Settings](/dashboard/user-settings). 2. Open your project's **Settings** tab. 3. Select a repository from the list of repositories that the Phemeral GitHub App can access. When you disconnect a repository, all branch-to-environment mappings for the project are removed. Existing deployments are not affected. See [Set Up GitHub Continuous Deployment](/docs/guides/github-integration) for the full setup guide. ## Project-Level Start Command [#project-level-start-command] Projects can optionally define a custom start command from the **Settings** tab. When a custom command is saved, new deployments for that project use it instead of Phemeral's autodetected `uvicorn` or `gunicorn` command. Clearing the saved value returns the project to autodetection. This setting is shared across the project. It does not change existing deployments that have already been created. See [Set a Custom Start Command](/docs/guides/custom-start-command) for the workflow and [Supported Frameworks](/docs/reference/supported-frameworks) for the autodetected defaults. ## Projects and Organizations [#projects-and-organizations] Each project belongs to a single organization. All members of the organization can access its projects. There is no per-project access control; access is managed at the organization level. # Connect Your GitHub Account (/docs/getting-started/connect-github) Before Phemeral can deploy code from your GitHub repositories, you need to connect your GitHub account and authorize the Phemeral GitHub App. ## Step 1: Open User Settings [#step-1-open-user-settings] Navigate to [User Settings](/dashboard/user-settings) in the Phemeral dashboard. ## Step 2: Connect GitHub [#step-2-connect-github] In the **GitHub** section, click **Connect**. This redirects you to GitHub to authorize the Phemeral OAuth integration. Sign in to GitHub (if not already signed in) and authorize the application. After authorizing, you will be prompted to install the **Phemeral GitHub App** on your GitHub account or organization. During installation, GitHub will ask you to choose which repositories the app can access. You can grant access to all repositories or select specific ones. ## Step 3: Verify the Connection [#step-3-verify-the-connection] After completing the GitHub authorization and app installation, return to [User Settings](/dashboard/user-settings). The GitHub section should show your account as connected. ## What Happens Next [#what-happens-next] With your GitHub account connected, you can now: * Connect a repository to a Phemeral project (from the project's **Settings** tab). * Map branches to environments for automatic deployments. Continue to [Deploy Your First Project](/docs/getting-started/first-deployment) for a step-by-step walkthrough. ## Disconnecting GitHub [#disconnecting-github] To disconnect your GitHub account, visit [User Settings](/dashboard/user-settings) and click **Disconnect** in the GitHub section. This revokes the connection. Any existing repository connections on your projects remain in place but will stop receiving new push events until a GitHub account is reconnected. # Deploy Your First Project (/docs/getting-started/first-deployment) This tutorial walks you through deploying a Python backend on Phemeral end-to-end. You will create a project, connect a GitHub repository, map a branch, and push a commit to trigger your first deployment. ## Prerequisites [#prerequisites] * A Phemeral account with a [connected GitHub account](/docs/getting-started/connect-github). * A GitHub repository containing a Python backend using FastAPI, Flask, or Django. See [Supported Frameworks](/docs/reference/supported-frameworks) for details. ## Step 1: Create a Project [#step-1-create-a-project] When you first sign up, Phemeral creates a default **Launchpad** project with **Production**, **Staging**, and **Development** environments. You can use this project or create a new one: 1. From the dashboard, click **New Project**. 2. Enter a **project name**. 3. On the **Environment Variables** step, optionally add any variables your application needs (e.g. `DATABASE_URL`, `SECRET_KEY`). **Production** is selected by default; switch the environment selector to add separate variables for **Staging** or **Development**. You can enter variables individually or import a `.env` file or pasted snippet. See [Manage Environment Variables](/docs/guides/environment-variables). 4. Click **Create Project**. You are redirected to the project page. ## Step 2: Connect a GitHub Repository [#step-2-connect-a-github-repository] 1. On your project page, go to the **Settings** tab. 2. In the **GitHub Repository** section, select the repository you want to deploy from the dropdown. This list shows repositories that Phemeral has access to. 3. Confirm the connection. The project is now linked to your repository. If your app later needs a different runtime command than the one Phemeral detects automatically, you can override it from the project's **Settings** tab. See [Set a Custom Start Command](/docs/guides/custom-start-command). ## Step 3: Map a Branch to an Environment [#step-3-map-a-branch-to-an-environment] Still on the **Settings** tab: 1. Find the **Branch Mappings** section. 2. Select a branch from your repository (e.g. `main`). 3. Select the environment to deploy to (e.g. **Development**). 4. Save the mapping. From now on, every push to the mapped branch triggers an automatic deployment to that environment. ## Step 4: Push Code and Deploy [#step-4-push-code-and-deploy] Push a commit to the branch you mapped (e.g. `main`): ```bash git add . git commit -m "Initial deployment" git push origin main ``` Phemeral receives the push event via the GitHub webhook and starts a deployment. ## Step 5: Monitor the Deployment [#step-5-monitor-the-deployment] 1. Navigate to your project in the dashboard. 2. Open the **Deployments** tab or click the latest deployment. 3. Watch the build progress and view logs. When the status changes to **Successful**, your deployment is live. ## Step 6: Visit Your Deployment [#step-6-visit-your-deployment] Every successful deployment receives a URL in the format: ``` {deployment-id}.phemeral.app ``` Click the deployment URL in the dashboard to visit your running application. If you assigned a custom domain to the environment, that domain also points to the new deployment. See [Configure Custom Domains](/docs/guides/custom-domains). ## Next Steps [#next-steps] * [Set Up GitHub Continuous Deployment](/docs/guides/github-integration): Map multiple branches to different environments. * [Manage Environment Variables](/docs/guides/environment-variables): Add secrets and configuration to your environments. * [Configure Custom Domains](/docs/guides/custom-domains): Give your environment a memorable subdomain. # Getting Started (/docs/getting-started) This section walks you through setting up Phemeral from scratch. By the end, you will have a Python backend deployed and accessible at a public URL. ## Prerequisites [#prerequisites] * A GitHub account with at least one repository containing a Python backend (FastAPI, Flask, or Django). * A Phemeral account. Sign up at [phemeral.dev](https://phemeral.dev). ## Steps [#steps] 1. **[Connect Your GitHub Account](/docs/getting-started/connect-github):** Authorize the Phemeral GitHub App so that Phemeral can access your repositories. 2. **[Deploy Your First Project](/docs/getting-started/first-deployment):** Connect a repository, map a branch, and push code to trigger your first deployment. # Configure Custom Domains (/docs/guides/custom-domains) Environment domains give your environment a memorable, stable URL that persists across deployments. Instead of using a deployment-specific URL that changes with each deploy, an environment domain always points to the environment's current deployment. Phemeral supports two kinds of environment domains: 1. **Custom domains:** Hostnames you own, such as `api.example.com`. 2. **Phemeral subdomains:** Vanity subdomains of `.phemeral.app`, such as `my-api.phemeral.app`. Both are assigned to an **environment**, not a specific deployment. Traffic is routed to whichever deployment is currently active on that environment, and when a new deployment is set as current, the domain automatically begins routing to it. ## Custom Domains [#custom-domains] Use a custom domain when you want your environment to be reachable via a hostname you already own (for example, `api.example.com` or `app.example.com`). ### How custom domains work [#how-custom-domains-work] * You can add any fully-qualified hostname you control, as long as it is not already in use by another Phemeral environment. * The domain is created in a **Pending DNS** state. Phemeral checks whether the hostname's DNS records point at the platform. * Once the DNS records are detected, the domain becomes **Active** and traffic is routed to the environment's current deployment. * If the DNS check fails, the domain remains in a pending state ### DNS requirements [#dns-requirements] Before adding a custom domain, configure your DNS provider to point the hostname at Phemeral using one or both of the following record types: | Record type | Target | | ----------- | ---------------------------------- | | **A** | Provided IPv4 addresses | | **AAAA** | Provided IPv6 addresses (optional) | The exact IP addresses are shown in the dashboard when you add a custom domain. ### Add a custom domain [#add-a-custom-domain] 1. Navigate to your project in the dashboard. 2. Open the environment you want to assign the domain to. 3. In the **Domains** section, click **Add Domain**. 4. Enter the full hostname (for example, `api.example.com`). If its DNS records are already pointing at Phemeral, it becomes **Active** right away. Otherwise it enters the **Pending DNS** state. ### Verify a custom domain [#verify-a-custom-domain] If a custom domain is pending, you can trigger a DNS recheck at any time: 1. In the **Domains** section, find the pending custom domain. 2. Click **Verify DNS**. If the DNS records are correct, the domain becomes **Active**. If not, fix the configuration and try again. ### Remove a custom domain [#remove-a-custom-domain] 1. Open the environment in the dashboard. 2. In the **Domains** section, find the custom domain you want to remove. 3. Click **Remove Domain**. Traffic to the removed domain will no longer be routed to your environment. ## Phemeral Subdomains [#phemeral-subdomains] Use a Phemeral subdomain when you want a clean, memorable URL under `.phemeral.app` without managing external DNS. ### How Phemeral subdomains work [#how-phemeral-subdomains-work] * Every environment automatically receives a default subdomain in the format `{project-name}-{environment-name}.phemeral.app` when it is created. * You can add additional vanity subdomains (for example, `api.phemeral.app` or `my-app.phemeral.app`) as long as they are not already taken. * Phemeral subdomains are **Active** immediately; no DNS setup is required. ### Add a Phemeral subdomain [#add-a-phemeral-subdomain] 1. Navigate to your project in the dashboard. 2. Open the environment you want to assign a subdomain to. 3. In the **Domains** section, click **Add Domain**. 4. Enter the subdomain you want, including the `.phemeral.app` suffix (for example, `my-app.phemeral.app`). If the subdomain is available, it is assigned to your environment immediately. Otherwise, choose a different subdomain and try again. ### Remove a Phemeral subdomain [#remove-a-phemeral-subdomain] 1. Open the environment in the dashboard. 2. In the **Domains** section, find the subdomain you want to remove. 3. Click **Remove Domain**. Traffic to the removed subdomain will no longer be routed to your environment. ## Multiple Domains [#multiple-domains] An environment can have any mix of custom domains and Phemeral subdomains. All of them route to the same current deployment. ## Deployment Domains vs. Environment Domains [#deployment-domains-vs-environment-domains] | Type | Format | Points to | | ------------------ | ------------------------------------ | ------------------------------------ | | Deployment domain | `{deployment-id}.phemeral.app` | A specific deployment (always) | | Environment domain | Any active custom or platform domain | The environment's current deployment | Deployment domains are auto-generated and immutable. They are useful for previewing or testing a specific deployment. Environment domains follow the current deployment as it changes. # Set a Custom Root Directory (/docs/guides/custom-root-directory) Use this guide when your application lives in a subdirectory and Phemeral's default project root detection is not the directory you want to deploy. ## Before You Begin [#before-you-begin] * A Phemeral project connected to a repository. * Access to the project's **Settings** tab. * The repository path to the Python service you want Phemeral to build and run. Phemeral saves the custom root directory at the **project** level. New deployments for that project use the saved directory until you clear it. ## When to Use a Custom Root Directory [#when-to-use-a-custom-root-directory] Most projects should keep autodetection enabled. Set a custom root directory only when Phemeral should look in a specific subdirectory for dependency files and your app entry point. Common cases include: * A monorepo where your Python backend lives under a folder such as `backend/` or `services/api/`. * A repository that contains multiple deployable services. * A repository where the shallowest dependency file belongs to a different tool or service than the one you want to deploy. ## Save a Custom Root Directory [#save-a-custom-root-directory] 1. Open your project in the dashboard. 2. Go to the **Settings** tab. 3. Find the **Root Directory** section. 4. Enter the path to your app directory, relative to the repository root. 5. Click **Save Root Directory**. After the directory is saved, the section shows the project as using **Custom** root detection. ## Path Requirements [#path-requirements] The saved path must: * Be relative to the repository root. * Point to a directory in the repository. * Stay inside the repository. The path must not: * Start with `/`. * Use `..` to move outside the repository. Examples: ```text backend services/api apps/customer-service ``` ## What Changes After You Save It [#what-changes-after-you-save-it] For future deployments, Phemeral starts dependency and app discovery inside the saved directory instead of searching from the repository root. Within that directory, Phemeral still uses the normal detection rules: * Dependency files are checked in this order: `uv.lock`, `poetry.lock`, `pyproject.toml`, then `requirements.txt`. * The shallowest matching dependency file inside the selected directory wins. * App discovery runs from that same directory. This is useful when your repository contains multiple Python projects but only one of them should be deployed by this Phemeral project. ## Trigger a New Deployment [#trigger-a-new-deployment] The updated root directory is used for **new** deployments. Existing deployments keep the configuration they were built with. To apply the change: * **With GitHub CD**: Push a new commit to a mapped branch. * **Without GitHub CD**: Create a new deployment from the dashboard. ## Clear the Custom Root Directory [#clear-the-custom-root-directory] To return to autodetection from the repository root: 1. Open the project's **Settings** tab. 2. In **Root Directory**, remove the saved value. 3. Click **Save Root Directory**. When the saved value is empty, Phemeral falls back to autodetecting the project root from the repository root. The section shows the project as using **Autodetect**. ## Notes [#notes] * The directory is repository-relative, not an absolute filesystem path. * If the saved directory does not exist in a future commit, that deployment will fail during project detection. * A custom root directory only changes where Phemeral looks for dependency files and the app entry point. It does not change your repository contents. * If you also need to override how the app starts, see [Set a Custom Start Command](/docs/guides/custom-start-command). # Set a Custom Start Command (/docs/guides/custom-start-command) Use this guide when your app needs a runtime command different from the one Phemeral detects automatically. ## Before You Begin [#before-you-begin] * A Phemeral project. * Access to the project's **Settings** tab. * A start command that binds your app to port `8000`. Phemeral saves the custom start command at the **project** level. New deployments for that project use the saved command until you clear it. ## When to Use a Custom Start Command [#when-to-use-a-custom-start-command] Most projects should keep autodetection enabled. Set a custom command only when you need to override the default `uvicorn` or `gunicorn` command that Phemeral derives from your app. Common cases include: * Your app starts through a wrapper script. * You need different runtime flags than the defaults. * Your app entry point is valid, but the detected server command is not the one you want to run. * A monorepo where you want to specify which service within the repo to run. ## Save a Custom Start Command [#save-a-custom-start-command] 1. Open your project in the dashboard. 2. Go to the **Settings** tab. 3. Find the **Start Command** section. 4. Enter the command you want Phemeral to run inside the deployment VM. 5. Click **Save Command**. After the command is saved, the section shows the project as using a **Custom** start command. ## Trigger a New Deployment [#trigger-a-new-deployment] The updated command is used for **new** deployments. Existing deployments keep the command they were built with. To apply the change: * **With GitHub CD**: Push a new commit to a mapped branch. * **Without GitHub CD**: Create a new deployment from the dashboard. ## Clear the Custom Command [#clear-the-custom-command] To return to autodetection: 1. Open the project's **Settings** tab. 2. In **Start Command**, remove the saved value. 3. Click **Save Command**. When the saved value is empty, Phemeral falls back to its autodetected runtime command. The section shows the project as using **Autodetect**. ## Example Commands [#example-commands] ```bash # FastAPI or Django uvicorn app.main:app --host 0.0.0.0 --port 8000 # Flask gunicorn app.main:app --bind 0.0.0.0:8000 ``` ## Notes [#notes] * The command should start a web server process that binds to port `8000`. Phemeral routes incoming requests to that port. * If you clear the command later, future deployments go back to using the autodetected default. See [Supported Frameworks](/docs/reference/supported-frameworks). # Manage Environment Variables (/docs/guides/environment-variables) Use environment variables to pass configuration and secrets to your application without hardcoding them in your source code. Each environment has its own independent set of variables. ## Before You Begin [#before-you-begin] * Environment variable values must be non-empty. * Each environment can have up to **200 environment variables**. * Changes apply to new deployments. Existing deployments keep the values they were built with. ## Add Variables During Project Creation [#add-variables-during-project-creation] 1. From the dashboard, click **New Project**. 2. Complete the project details until you reach **Environment Variables**. 3. Use the environment selector to choose the variable group you want to edit. **Production** is selected by default. 4. Click **Add variable**, then enter a key and value. 5. Switch between **Production**, **Staging**, and **Development** to add variables to the other environment groups. 6. Click **Create Project**, or continue to the advanced settings before creating the project. ## Add Variables While Creating an Environment [#add-variables-while-creating-an-environment] 1. Open the project's **Environments** page. 2. Click **New Environment**. 3. Enter the environment name. 4. Under **Environment variables (optional)**, click **Add variable** and enter a key and value. 5. Click **Create environment**. ## Manage an Existing Environment [#manage-an-existing-environment] 1. Open the environment in the dashboard. 2. In **Environment Variables**, click **Manage Env Vars**. 3. In the side panel, edit existing keys or values, click **Add variable** to add a row, or use the remove button beside a row to delete it. 4. Review the complete variable set. 5. Click **Save changes**. The side panel loads the environment's complete variable set. Closing the side panel or clicking **Cancel** discards unsaved changes. ## Import a `.env` File or Snippet [#import-a-env-file-or-snippet] You can import variables during project creation, while creating an environment, or from an existing environment's management panel. 1. Select the environment or environment group you want to populate. 2. Click **Import from .env**. 3. Click **Choose file** to select a local `.env` file, or paste `.env` content into the text area. 4. Leave **Override value of existing env vars with imported env vars** unchecked, or select it to replace existing values: | Setting | Matching existing keys | New keys | | ---------------------------- | -------------------------------------------------- | ------------------------- | | Override unchecked (default) | Keep the existing value | Add the imported variable | | Override checked | Replace the existing value with the imported value | Add the imported variable | 5. Click **Import env vars**. 6. Review the populated form, then save or submit the parent form. Importing only changes the current form draft; no variables are saved until you submit the parent form. Imported files and pasted content must be no larger than **1 MB**, and an import cannot bring the selected environment above its 200-variable limit. ## Key Format Requirements [#key-format-requirements] Environment variable keys must: * Start with a letter (`a-z`, `A-Z`) or underscore (`_`). * Contain only letters, digits (`0-9`), and underscores. * Be unique within the environment. Valid examples include `DATABASE_URL`, `SECRET_KEY`, `_INTERNAL_FLAG`, and `API_KEY_V2`. Every variable with a key must have a non-empty value. ## View a Variable's Value [#view-a-variables-value] Values in the environment summary are hidden by default: 1. Open the environment in the dashboard. 2. In **Environment Variables**, click the reveal control beside the variable you want to inspect. Opening **Manage Env Vars** retrieves the current values and displays them in editable fields. ## Redeploy After Changes [#redeploy-after-changes] After you add, change, or remove a variable, create a new deployment for the change to take effect: * **With GitHub CD**: Push a commit to the mapped branch. * **Without GitHub CD**: Create a new deployment from the dashboard. # Set Up GitHub Continuous Deployment (/docs/guides/github-integration) This guide covers how to set up continuous deployment so that pushing to a GitHub branch automatically deploys your code to a Phemeral environment. ## Prerequisites [#prerequisites] * A Phemeral project. See [Deploy Your First Project](/docs/getting-started/first-deployment) if you haven't created one. * A [connected GitHub account](/docs/getting-started/connect-github) with the Phemeral GitHub App installed. ## Connect a Repository to Your Project [#connect-a-repository-to-your-project] 1. Open your project in the dashboard. 2. Go to the **Settings** tab. 3. In the **GitHub Repository** section, select the repository from the dropdown. The dropdown lists all repositories that Phemeral has access to across your GitHub account and any of your GitHub organizations where Phemeral has been connected. A project can be connected to one repository at a time. ## Map a Branch to an Environment [#map-a-branch-to-an-environment] After connecting a repository: 1. On the **Settings** tab, find the **Branch Mappings** section. 2. Select a **branch** from the repository. 3. Select the **environment** you want that branch to deploy to. 4. Save the mapping. You can map multiple branches to different environments within the same project. For example: | Branch | Environment | | ---------------------- | ----------- | | `main` | Production | | `pre-release` | Staging | | `fancy-feature-branch` | Development | Each branch can be mapped to one environment. Each environment can have one branch mapped to it. ## Remove a Branch Mapping [#remove-a-branch-mapping] To stop automatic deployments for a branch: 1. Go to the project's **Settings** tab. 2. Find the branch mapping you want to remove. 3. Delete the mapping. Existing deployments are not affected. The environment retains its current deployment. ## Disconnect a Repository [#disconnect-a-repository] To remove the repository connection entirely: 1. Go to the project's **Settings** tab. 2. Click **Disconnect** in the GitHub Repository section. This removes **all** branch-to-environment mappings for the project. Existing deployments and environments are not affected, but no new automatic deployments will be triggered until a repository is reconnected. # Create and Connect to a Database Cluster (/docs/guides/managed-postgres) A **database cluster** is a fully managed Postgres database that lives inside one of your projects. This guide walks through creating a cluster, retrieving its connection string, connecting your application, and deleting the cluster when you no longer need it. For background on how clusters scale and how they are billed, see [Database Clusters](/docs/concepts/database-clusters) and the [Managed Postgres reference](/docs/reference/managed-postgres). ## Prerequisites [#prerequisites] * An organization on the **Pro** plan. * A Phemeral project. ## Create a Database Cluster [#create-a-database-cluster] 1. Open your project in the dashboard. 2. Go to the **Database Clusters** tab. 3. Click **New Cluster**. 4. Enter a **cluster name**. Names may contain letters, numbers, spaces, and hyphens, and must be unique within the project. 5. Click **Create Cluster**. Phemeral provisions the cluster and it appears in the project's cluster list. A project can hold up to **20 database clusters**. ## Get the Connection String [#get-the-connection-string] Each cluster exposes a **connection URI** that you can use to connect to your cluster. 1. Open the project's **Database Clusters** tab and select the cluster. 2. On the cluster's detail page, find the **Connection URI** field. 3. Click the reveal (eye) button to display the connection string. The **pooled connection string** is selected by default and is what you should use for most applications. It supports up to **10,000 concurrent connections** and lets you connect directly without running your own connection pooler. If you need a **direct connection string** instead (for example, for administrative tools or long-running sessions), you can uncheck the **Pooled connection URI** option before revealing the string. Treat the connection URI as a secret. ## Connect Your Application [#connect-your-application] Use the connection URI anywhere you would normally configure a Postgres connection. For example, set it as an environment variable such as `DATABASE_URL` on the environment that needs database access, then read it in your application code. ## Delete a Database Cluster [#delete-a-database-cluster] Deleting a cluster permanently destroys the database and all of its data. This cannot be undone! 1. Open the project's **Database Clusters** tab and select the cluster. 2. Click **Delete Cluster**. 3. Type `delete` to confirm. 4. Click **Confirm Deletion**. Deleting a cluster frees one of the project's 20 cluster slots. # Trigger a Manual Deployment (/docs/guides/manual-deployments) Use this guide when you want to deploy without pushing to a mapped branch. A **manual deployment** lets you start a build on demand, either by uploading a local folder or by deploying a branch from a connected GitHub repository. ## Before You Begin [#before-you-begin] * A project with at least one environment. * The code you want to deploy, either as a local folder or in a connected GitHub repository. Manual deployments complement [GitHub continuous deployment](/docs/guides/github-integration). You can use both on the same project: push to a mapped branch for automatic deploys, and trigger a manual deployment whenever you need an off-cycle build. ## When to Use a Manual Deployment [#when-to-use-a-manual-deployment] Most teams rely on automatic deployments triggered by pushes to a mapped branch. Trigger a manual deployment when you want to: * Deploy local changes that you have not pushed to GitHub yet. * Deploy a branch on demand, without configuring a branch mapping. * Re-run a build for an existing branch * Deploy a project that is not connected to GitHub at all, using a folder upload. ## Open the Deployment Dialog [#open-the-deployment-dialog] 1. Open your project in the dashboard. 2. Scroll to the **Recent Deployments** section. 3. Click **New Deployment**. The **Deploy Code** dialog opens with two tabs: **File Upload** and **GitHub Repo**. Choose the path that fits your source code. ## Option A: Deploy from a Local Folder [#option-a-deploy-from-a-local-folder] Use this path to deploy code straight from your machine. No GitHub connection is required. 1. In the **Deploy Code** dialog, select the **File Upload** tab. 2. Click **Choose Folder** and select the folder that contains your project. 3. Select the target **Environment** from the dropdown. 4. Leave **Set as current deployment** checked to route the environment's traffic to this deployment once it succeeds. Uncheck it to build the deployment without changing which deployment currently serves traffic. See [Set as the Current Deployment](#set-as-the-current-deployment). 5. Click **Deploy**. ## Option B: Deploy from a Connected GitHub Repository [#option-b-deploy-from-a-connected-github-repository] Use this path to deploy a specific branch on demand. This path requires a connected GitHub repository. 1. In the **Deploy Code** dialog, select the **GitHub Repo** tab. 2. Confirm the connected repository shown at the top of the tab (**Deploy from connected repo: `owner/repo`**). 3. Select the target **Environment** from the dropdown. 4. Select the **Branch** to deploy. The list is populated with the branches in your connected repository; the default branch is preselected. 5. Leave **Set as current deployment** checked to route traffic to this deployment once it succeeds, or uncheck it to build without changing the current deployment. 6. Click **Deploy from GitHub**. ### If No Repository Is Connected [#if-no-repository-is-connected] If the project has no connected GitHub repository, the **GitHub Repo** tab shows **No GitHub repository connected**. Click **Open Project Settings** to connect one, then return to this dialog. See [Set Up GitHub Continuous Deployment](/docs/guides/github-integration). # Create & Switch Organizations (/docs/guides/organizations) An **organization** is the top-level container for your work in Phemeral. Each organization has its own projects, environments, team members, API keys, and billing. You can belong to more than one organization, such as a personal organization and one for a company, and switch between them at any time. Your first organization is created automatically when you sign up. This guide covers creating additional organizations and moving between them. ## The Organization Menu [#the-organization-menu] Everything related to organizations lives in the **organization menu** in the top-right of the dashboard, labeled with your current organization's name. Opening it shows: * The organizations you belong to, with the active one marked. * A **+** button next to the **Organization** label for creating a new organization. * Links to **Member Management**, **API Keys**, and **Org Settings** for the active organization. ## Create an Organization [#create-an-organization] 1. Open the **organization menu** in the top-right of the dashboard. 2. Click the **+** button next to the **Organization** label. 3. Enter an **Organization Name**. 4. Choose a **plan**: * **Free:** The organization is created and you are switched into it immediately. * **Pro:** You are taken to checkout. After you complete payment, you land in the new organization. If you cancel, the organization is still created on the Free plan, and you can upgrade later. 5. Select **Create Organization** (or **Create & Continue to Checkout** for a paid plan). See [Plans & Billing](/docs/reference/plans-and-billing) for a full comparison of the Free and Pro tiers. ### What a New Organization Includes [#what-a-new-organization-includes] Every organization you create starts with: * A default project called **Launchpad**, with **Production**, **Staging**, and **Development** environments. See [Projects](/docs/concepts/projects). * Your account as its first member. See [Manage Team Members](/docs/guides/team-management). * Its own billing, starting on the **Free** plan until you complete checkout for a paid plan. ## Switch Between Organizations [#switch-between-organizations] 1. Open the **organization menu** in the top-right of the dashboard. 2. Select the organization you want to switch to. The active organization is marked with a check. ## Organization Access [#organization-access] Access is managed per organization; there is no shared access across organizations. Projects, environments, members, API keys, and billing are all scoped to a single organization. To give someone access to an organization, invite them as a member from **Member Management**. See [Manage Team Members](/docs/guides/team-management). # Configure Scheduled Webhooks (/docs/guides/scheduled-webhooks) Scheduled webhooks let Phemeral send HTTP requests to one of your environments on a recurring schedule. This is useful for tasks like warming caches, triggering internal maintenance endpoints, or running application-level jobs without relying on an external cron worker. ## Prerequisites [#prerequisites] * A Phemeral project with at least one environment. * At least one active domain on the environment you want to target. * An application endpoint that can safely be called on a schedule. ## How Scheduled Webhooks Work [#how-scheduled-webhooks-work] Scheduled webhooks are configured from the project's **Settings** tab, but each webhook targets a single **environment**. When a scheduled webhook runs, Phemeral: 1. Resolves the selected environment's oldest active domain. 2. Sends the configured HTTP request to the relative path you provided. 3. Routes that request to the environment's **current deployment**. This means the webhook follows the environment as new deployments become current. You do not need to update the webhook after each deployment. ## Add a Scheduled Webhook [#add-a-scheduled-webhook] 1. Open your project in the dashboard. 2. Go to the **Settings** tab. 3. Find the **Scheduled Webhooks** section. 4. Click **Add Scheduled Webhook**. 5. Enter a **name**. 6. Select the **environment** to target. 7. Enter the **relative path** to call, such as `/api/cron/daily`. 8. Choose the HTTP **method**. 9. Enter a **UTC 6-field cron expression**. 10. Optionally configure headers, a request body, expected status codes, a start time, an end time, retries, or the disabled state. 11. Save the webhook. If the selected environment does not have an active domain, Phemeral blocks the save until one is available. ## Cron Format [#cron-format] Scheduled webhooks use a UTC **6-field** cron expression in 6 field format: ```text second minute hour day-of-month month day-of-week ``` Examples: | Schedule | Expression | | ------------------------- | ---------------- | | Every 30 seconds | `*/30 * * * * *` | | Every hour at minute 15 | `0 15 * * * *` | | Every day at 03:00 UTC | `0 0 3 * * *` | | Every Monday at 09:30 UTC | `0 30 9 * * 1` | All cron expressions are interpreted in UTC. ## Optional Request Controls [#optional-request-controls] You can customize how Phemeral executes each scheduled webhook with the following optional settings: * **Headers**: Add custom request headers. * **Body**: Send a request body for methods like `POST`, `PUT`, or `PATCH`. * **Expected status codes**: Provide a comma-separated list such as `200,201,204`. Any other response is treated as a failed execution. * **Retries**: Retry failed executions a fixed number of times. * **Starts at / Expires at**: Limit when the schedule is active using UTC ISO 8601 timestamps such as `2026-05-01T00:00:00Z`. * **Disabled**: Keep the webhook saved without running it. If you leave **Expected status codes** empty, Phemeral accepts any HTTP status code as a successful execution. ## Edit, Disable, or Delete a Scheduled Webhook [#edit-disable-or-delete-a-scheduled-webhook] 1. Open the project's **Settings** tab. 2. In **Scheduled Webhooks**, find the webhook you want to change. 3. Choose one of the following: * **Edit** to update the request configuration or schedule. * **Disable** from the edit dialog to keep the webhook without executing it. * **Delete** to remove it entirely. Changes take effect on future runs. Deleting a scheduled webhook stops future executions. ## Environment Domain Behavior [#environment-domain-behavior] Scheduled webhooks require an active environment domain. * New webhooks cannot be saved unless the selected environment has at least one active domain. * Phemeral always targets the environment's oldest active domain. * If the active domain set changes, Phemeral resyncs the webhook to the current canonical active domain. * If an environment temporarily has no active domains, the scheduled webhook remains saved but does not run until an active domain is available again. ## Observing Scheduled Webhook Traffic [#observing-scheduled-webhook-traffic] Scheduled webhook requests are normal HTTP requests to your application. You can inspect their effects in the same places you inspect other traffic: * **Runtime logs** on the deployment detail page. * **Application-specific logs** emitted by the endpoint you called. * Any side effects produced by your handler, such as cache refreshes or background task creation. Because the request is routed through the environment's current deployment, the runtime logs appear on the deployment that actually handled the request. # Manage Team Members (/docs/guides/team-management) Phemeral organizations support multiple team members. Access is managed at the organization level, so all members of an organization can access all of its projects and environments. ## Invite a Team Member [#invite-a-team-member] 1. Navigate to **Member Management** from the dashboard sidebar. 2. Use the member management interface to invite a new member by email. 3. The invited user receives an email to join the organization. Invited members appear with a **pending** status until they accept the invitation. ## Seat Limits [#seat-limits] Your organization's plan determines how many team members (seats) you can have: | Plan | Included Seats | Seat Limit | | ---- | -------------- | ---------- | | Free | 1 | 1 | | Pro | Unlimited | Unlimited | * **Free plan**: Limited to 1 seat (the organization owner). You cannot invite additional members. * **Pro plan**: Unlimited seats are included. If your organization is at its seat limit, the member management page displays a warning and invitations are blocked. Upgrade your plan to add more seats. ## Check Seat Availability [#check-seat-availability] The **Member Management** page shows: * The number of **active seats** currently in use. * The number of **included seats** in your plan. * The **seat limit** for your plan. * Whether you are eligible to invite new members. ## Remove a Member [#remove-a-member] Use the member management interface to remove a member from the organization. Removing a member frees up a seat. The removed member immediately loses access to all projects and environments in the organization. ## Upgrading for More Seats [#upgrading-for-more-seats] If you need more team members than your current plan allows, you can upgrade from the **Pricing** page or through the billing portal. See [Plans & Billing](/docs/reference/plans-and-billing) for details on available plans. # WebSockets & Server-Sent Events (/docs/guides/websockets-and-sse) Phemeral supports long-lived, real-time connections to your deployments using both **WebSockets** and **Server-Sent Events (SSE)**. * **Server-Sent Events (SSE):** A one-way stream from your server to the client over a single HTTP response. Good for live feeds, notifications, progress updates, and token streaming. * **WebSockets:** A two-way connection between client and server. Good for chat, collaborative editing, multiplayer, and anything where the client also sends messages. ## Framework Support [#framework-support] WebSockets require an ASGI app (e.g., FastAPI or Django with Channels). Flask runs under WSGI, which has no WebSocket support. Use an ASGI framework if you need WebSockets. SSE works on every supported framework. Flask runs under gunicorn with a single synchronous worker by default. Each open SSE stream holds that worker for the life of the connection, blocking other requests to the deployment. To serve more than one or two concurrent streams, raise the worker count (or use an async worker) with a [custom start command](/docs/guides/custom-start-command). ```bash gunicorn app.main:app --bind 0.0.0.0:8000 --workers 4 ``` ## Keep Connections Alive [#keep-connections-alive] Phemeral closes a connection that has sent no data in either direction for more than five minutes. To keep a connection open, make sure something flows across it on an interval. **WebSockets:** send a ping/pong frame (or any message) **at least once every 5 minutes**. If more than five minutes pass with no traffic, the connection is dropped. Most servers do this for you, uvicorn sends WebSocket pings automatically (every 20 seconds by default), which keeps the connection alive with no extra code. If you turn automatic pings off, send an application-level message at least once every 5 minutes yourself. **Server-Sent Events:** emit an event or a comment line at least once every 5 minutes. A comment is any line beginning with a colon, which clients ignore: ```python yield ": keepalive\n\n" # emit on an interval to hold the connection open ``` ## Reconnect After Disconnects [#reconnect-after-disconnects] Design your client to reconnect automatically: * **SSE:** The browser's `EventSource` reconnects on its own by default, so most SSE clients need no extra handling. * **WebSockets:** Add reconnect logic to your client: when the socket closes, open a new one, ideally with a short backoff between attempts. ## How Real-Time Connections Are Billed [#how-real-time-connections-are-billed] A WebSocket or SSE connection is metered across Phemeral's standard [compute dimensions](/docs/reference/plans-and-billing): * **Requests:** Each connection counts as one request when it opens. * **Compute:** An open connection keeps your deployment running, so it accrues compute (RAM GB-hours) for the whole time it stays open, including idle periods with no messages. * **Data transfer:** Outbound data sent from your app to the client over the connection is metered. Because an open connection keeps your deployment from scaling to zero, many long-lived or idle connections can accrue compute charges even when little data is flowing. Close connections your client no longer needs. # Managed Postgres (/docs/reference/managed-postgres) Managed Postgres provides fully managed **database clusters** inside your projects. For a conceptual overview see [Database Clusters](/docs/concepts/database-clusters), and for setup steps see [Create and Connect to a Database Cluster](/docs/guides/managed-postgres). Managed Postgres is available for organizations on the **Pro** plan. ## Engine [#engine] | Property | Value | | ---------------------- | ------------------------------------------ | | Database engine | PostgreSQL 18 | | Connection pooling | Available by default (pooled URI) | | Concurrent connections | Up to 10,000 per cluster (with pooled URI) | ## Scaling [#scaling] Clusters scale compute automatically with load. | Property | Value | | ------------- | ------------------------------- | | Compute range | 0.25–8 vCPU | | Memory range | 1 GB–32 GB RAM | | Scale-to-zero | After 5 minutes with no traffic | A cluster scaled to zero wakes automatically on the next connection, so the first query after an idle period transparently scales it back up. Clusters are deployed geographically close to their project's compute. ## Limits [#limits] | Limit | Value | | ----------------------- | ------------------------------------- | | Clusters per project | 20 | | Cluster name characters | Letters, numbers, spaces, and hyphens | | Cluster name uniqueness | Must be unique within the project | ## Billing [#billing] Managed Postgres is billed by usage across three dimensions. | Dimension | Unit | Description | | ------------- | ------------ | --------------------------------------------------- | | Compute | RAM GB-hours | Memory allocated to the cluster, measured over time | | Storage | GB-hours | Data stored in the cluster, measured over time | | Data transfer | GB | Outbound data transferred from the cluster | Because clusters scale to zero when idle, a cluster that is not serving traffic accrues **no compute charge** for that period. Storage continues to be metered while data is retained. If your organization's managed Postgres usage exceeds the plan's limits, clusters are suspended until the next billing cycle. See [Plans & Billing](/docs/reference/plans-and-billing) for how managed Postgres fits into your subscription. ### Deletion [#deletion] Clusters are permanently deleted in any of the following cases: * You delete a cluster manually. * Your organization **downgrades to a plan that does not support managed Postgres**. * Your organization's **subscription is canceled**. Deletion is irreversible. Back up any data you need before downgrading or canceling. # Plans & Billing (/docs/reference/plans-and-billing) Phemeral offers a Free tier and a Pro tier. Plans are billed per organization.
## Free Tier [#free-tier] The Free tier is assigned to every new organization. It includes a set amount of compute resources each month. When your usage reaches the included limits, deployments are paused until the next billing cycle. * **1 team member** (the organization owner). * **Unlimited projects and deployments**, subject to compute limits. * Resource caps: 1M requests, 10 GB-hours compute, 10 GB data transfer, 500 build minutes. ## Pro Tier [#pro-tier] The Pro tier removes seat limits and switches to usage-based billing beyond a monthly credit. * **Unlimited team members**. * **$20/month compute credit** applied to usage-based charges. * Usage beyond the credit is billed at the per-unit rates listed above. * No hard pausing: your deployments continue running and you are billed for actual usage. * **100-day build cache retention** (vs. 10 days on Free). ## Compute Dimensions [#compute-dimensions] Phemeral tracks resource usage across four dimensions: | Dimension | Unit | Description | | ------------- | --------------- | -------------------------------------------------------- | | Requests | Per 1M requests | HTTP requests to your deployments | | Compute | RAM GB-hours | Memory allocated to your running VMs, measured over time | | Data transfer | GB | Outbound data from your deployments | | Build minutes | Minutes | Time spent building your deployment images | ### Long-Lived Connections (WebSockets & SSE) [#long-lived-connections-websockets--sse] WebSocket and Server-Sent Events connections are metered across the same dimensions as ordinary traffic: * Each connection counts as **one request** when it opens. * An open connection keeps your deployment running, so it accrues **compute (RAM GB-hours)** for the entire time it stays open, including idle periods with no messages. * **Data transfer** is metered on outbound data sent to the client. See [WebSockets & Server-Sent Events](/docs/guides/websockets-and-sse) for keep-alive and reconnection guidance. ## Managed Postgres [#managed-postgres] Managed Postgres **database clusters** are billed on a usage-based model. Phemeral meters three dimensions per cluster: | Dimension | Unit | Description | | --------- | ------------ | --------------------------------------------------- | | Compute | RAM GB-hours | Memory allocated to the cluster, measured over time | | Storage | GB-hours | Data stored in the cluster, measured over time | | Egress | GB | Outbound data transferred from the cluster | Clusters scale to zero after five minutes without traffic, so a cluster that is not serving traffic accrues **no compute charge** while it is scaled to zero. Storage continues to be metered while data is retained. ## Upgrading [#upgrading] To upgrade from Free to Pro: 1. Go to the **Pricing** page or your organization's billing settings. 2. Select the **Pro** plan. 3. Complete checkout. After checkout, your plan is immediately updated. The monthly compute credit is applied. ## Managing Your Subscription [#managing-your-subscription] Once on a paid plan, you can manage your subscription through the **Customer Portal**: * View invoices and payment history. * Update your payment method. * Cancel your subscription. To access the portal, go to your organization's billing settings and click **Manage Subscription**. ## Billing Cycle [#billing-cycle] Subscriptions are billed monthly. At each renewal: * Compute usage for the previous period is finalized. * A new monthly compute credit is applied. * An invoice is generated for any usage beyond the credit. ## Payment Failures [#payment-failures] If a payment fails: * Your subscription enters a **past due** state. * Phemeral retries the charge over a period of time. * If payment is recovered, your subscription returns to active status. * If payment is not recovered, the subscription is eventually canceled. ## Cancellation [#cancellation] When you cancel a paid subscription: * Your organization reverts to the Free tier at the end of the current billing period. * Existing deployments may be affected by Free tier resource limits following the downgrade at the end of the current billing period. * No further charges are incurred after the current billing period. # Project Structure Requirements (/docs/reference/project-structure) Phemeral analyzes your repository to determine how to build and run your application. This page describes what Phemeral looks for and how to structure your project for successful detection. ## Project Root Detection [#project-root-detection] The **project root** is the directory that contains your dependency file. Phemeral searches for dependency files in this priority order: 1. `uv.lock` 2. `poetry.lock` 3. `pyproject.toml` 4. `requirements.txt` The search walks the directory tree from the repository root, favoring files at shallower depths. The directory containing the first matching file becomes the project root. If autodetection picks the wrong service in a monorepo, you can override it in your project's **Root Directory** setting. See [Set a Custom Root Directory](/docs/guides/custom-root-directory). ### Example [#example] Given this repository structure: ``` my-repo/ ├── README.md ├── backend/ │ ├── pyproject.toml │ ├── uv.lock │ └── app/ │ └── main.py └── frontend/ └── ... ``` Phemeral finds `uv.lock` at `backend/uv.lock` and sets the project root to `backend/`. ## Custom Root Directory Override [#custom-root-directory-override] When you save a custom root directory in project settings, Phemeral starts detection from that repository-relative directory instead of from the repository root. Inside the selected directory, Phemeral keeps the same dependency priority rules: 1. `uv.lock` 2. `poetry.lock` 3. `pyproject.toml` 4. `requirements.txt` This lets you deploy a specific service from a monorepo without restructuring the repository. ## Directory Structure Examples [#directory-structure-examples] ### FastAPI with uv [#fastapi-with-uv] ``` my-project/ ├── .python-version ├── pyproject.toml ├── uv.lock └── app/ └── main.py # app = FastAPI() ``` ### Flask with poetry [#flask-with-poetry] ``` my-project/ ├── .python-version ├── pyproject.toml ├── poetry.lock └── app/ └── main.py # app = Flask(__name__) ``` ### Django with pip [#django-with-pip] ``` my-project/ ├── .python-version ├── requirements.txt └── myproject/ ├── manage.py ├── myproject/ │ ├── settings.py │ ├── urls.py │ └── asgi.py # application = get_asgi_application() └── myapp/ └── ... ``` ### Monorepo (Backend in Subdirectory) [#monorepo-backend-in-subdirectory] ``` my-repo/ ├── frontend/ │ └── ... └── backend/ ├── pyproject.toml ├── uv.lock └── app/ └── main.py # app = FastAPI() ``` Phemeral detects `backend/` as the project root because `uv.lock` is located there. ## Python Version [#python-version] Place a `.python-version` file at or near your project root containing the Python version: ``` 3.11 ``` Phemeral searches for this file starting at the project root. If not found, Python **3.12** is used as the default. ## Application Entry Point [#application-entry-point] Phemeral scans `.py` files in your project for framework instantiation. Ensure your framework instance is assigned to a module-level variable: ```python # app/main.py from fastapi import FastAPI app = FastAPI() # Phemeral detects "app" as the object name @app.get("/") def read_root(): return {"message": "Hello, World!"} ``` ## Checklist [#checklist] Before deploying, verify that your project includes: * [ ] A dependency file (`uv.lock`, `poetry.lock`, `pyproject.toml`, or `requirements.txt`) * [ ] A `.py` file that imports and instantiates a supported framework (FastAPI, Flask, or Django) * [ ] The framework instance is assigned to a module-level variable * [ ] Optionally, a `.python-version` file specifying your Python version * [ ] If needed, a custom root directory saved in project settings for monorepo or multi-service repositories # Scheduled Webhooks Reference (/docs/reference/scheduled-webhooks) This reference describes the fields and execution rules for Phemeral scheduled webhooks. ## Overview [#overview] A scheduled webhook is a saved HTTP request that Phemeral executes on a UTC cron schedule against a selected environment. Each scheduled webhook is: * configured from the project's **Settings** tab * bound to one **environment** * executed against that environment's oldest active domain * routed to the environment's **current deployment** For setup instructions, see [Configure Scheduled Webhooks](/docs/guides/scheduled-webhooks). ## Cron Syntax [#cron-syntax] Scheduled webhooks use a UTC **6-field** cron expression. ```text second minute hour day-of-month month day-of-week ``` ### Examples [#examples] | Meaning | Expression | | ------------------------- | ---------------- | | Every 10 seconds | `*/10 * * * * *` | | Every 30 seconds | `*/30 * * * * *` | | Every hour at minute 15 | `0 15 * * * *` | | Every day at 03:00 UTC | `0 0 3 * * *` | | Every Monday at 09:30 UTC | `0 30 9 * * 1` | All schedules are interpreted in UTC. ## Field Reference [#field-reference] | Field | Required | Description | | --------------------- | -------- | ---------------------------------------------------------------- | | Name | Yes | Human-readable label shown in the dashboard. | | Environment | Yes | The environment whose current deployment receives the request. | | Relative path | Yes | Request path beginning with `/`, such as `/api/jobs/daily`. | | Method | Yes | HTTP method used for the request. | | Schedule | Yes | UTC 6-field cron expression. | | Headers | No | Custom HTTP headers sent with the request. | | Body | No | Request body sent for methods such as `POST`, `PUT`, or `PATCH`. | | Expected status codes | No | Comma-separated HTTP status codes treated as successful. | | Starts at | No | UTC ISO 8601 timestamp after which the schedule becomes active. | | Expires at | No | UTC ISO 8601 timestamp after which the schedule stops running. | | Retries | No | Number of retry attempts after a failed execution. | | Disabled | No | When enabled, the webhook remains saved but does not run. | ## Method Values [#method-values] Scheduled webhooks support these HTTP methods: * `GET` * `POST` * `PUT` * `PATCH` * `DELETE` * `HEAD` * `OPTIONS` ## Expected Status Codes [#expected-status-codes] If **Expected status codes** is provided, Phemeral treats only those response codes as successful. Examples: * `200` * `200,201` * `200,201,202,204` If the field is left empty, any HTTP status code is accepted as successful. Each status code must be between `100` and `599`. ## Time Window Rules [#time-window-rules] `Starts at` and `Expires at` must both be UTC ISO 8601 timestamps when provided. Examples: * `2026-05-01T00:00:00Z` * `2026-05-01T12:30:00+00:00` Rules: * Timestamps must include timezone information. * The timezone must be UTC. * If both fields are set, `starts_at` must be earlier than `expires_at`. ## Domain Resolution [#domain-resolution] Scheduled webhooks run only when the selected environment has at least one active domain. Execution uses the environment's oldest active domain in canonical order. Behavior: * A new scheduled webhook cannot be saved without an active domain. * If the active domain set changes, Phemeral resyncs the scheduled webhook. * If no active domain is available, the webhook remains saved but cannot execute until a domain becomes active again. ## Execution Semantics [#execution-semantics] When a scheduled webhook fires, Phemeral sends the configured request to the selected environment's active domain and relative path. Because the request targets the environment rather than a fixed deployment: * the webhook automatically follows new current deployments * you do not need to update the webhook after each deploy * runtime logs appear on whichever deployment handled that request ## Validation Rules [#validation-rules] Phemeral applies these validation rules when saving a scheduled webhook: * name must be non-empty * environment must belong to the selected project * path must be a non-empty relative path * schedule must be a valid UTC 6-field cron expression * retries must be zero or greater * expected status codes must be valid HTTP status codes * `starts_at` and `expires_at` must be valid UTC timestamps ## Runtime Visibility [#runtime-visibility] Scheduled webhook requests are regular HTTP requests to your application. You can inspect them through: * deployment runtime logs * application logs emitted by the endpoint * application-side effects such as writes, cache refreshes, or background work # Supported Frameworks (/docs/reference/supported-frameworks) Phemeral automatically detects your Python framework and package manager from your source code. No configuration files are required because Phemeral analyzes your project structure and code to determine how to build and run your application. ## Frameworks [#frameworks] | Framework | Server | Protocol | | --------- | -------- | -------- | | FastAPI | uvicorn | ASGI | | Flask | gunicorn | WSGI | | Django | uvicorn | ASGI | ## Package Managers [#package-managers] | Package Manager | | ---------------------- | | uv | | poetry | | pip (pyproject.toml) | | pip (requirements.txt) | ### Detection Priority [#detection-priority] Phemeral searches for dependency files in this order and uses the first match: 1. `uv.lock` → uv 2. `poetry.lock` → poetry 3. `pyproject.toml` → pip 4. `requirements.txt` → pip The search finds the dependency file closest to the root of your repository (shallowest directory depth). This file's location also determines the **project root**, which is the directory that Phemeral treats as the working directory for your application. ## Python Version [#python-version] Phemeral reads the Python version from a `.python-version` file in your project. If no `.python-version` file is found, Python 3.12 is used as the default. The `.python-version` file should contain a version number. For example: ``` 3.11 ``` ## Runtime Servers [#runtime-servers] Based on the detected framework protocol: * **ASGI** (FastAPI, Django): Your application is started with **uvicorn** on port 8000. * **WSGI** (Flask): Your application is started with **gunicorn** on port 8000. ## Runtime Command Selection [#runtime-command-selection] Phemeral chooses the runtime command for each new deployment in this order: 1. If the project has a saved **custom start command**, Phemeral uses that command. 2. Otherwise, Phemeral uses the autodetected default command for the framework. You can set or clear the custom command from the project's **Settings** tab. Saving an empty value returns the project to autodetection. See [Set a Custom Start Command](/docs/guides/custom-start-command). The autodetected fallback command follows this pattern: ```bash # ASGI (FastAPI, Django) uvicorn {module}:{object} --host 0.0.0.0 --port 8000 # WSGI (Flask) gunicorn {module}:{object} --bind 0.0.0.0:8000 ``` Custom start commands should also bind your application to port `8000`.