---
title: "How to deploy a site to Netlify | Netlify Knowledge Base"
description: "Guides and articles to help you get the most out of the Netlify platform."
source: "https://www.netlify.com/knowledge-base/how-to-deploy-a-site-to-netlify/"
last_updated: "2026-09-24T09:05:27.000Z"
---
## Key takeaways

Connect your Git repo at [app.netlify.com/start](https://app.netlify.com/start) and Netlify builds and deploys on every push, with a Deploy Preview for every pull request. That’s the path you want for anything you’ll work on more than once. If you need a one-off deploy instead, run `netlify deploy` for a draft URL or `netlify deploy --prod` to publish, and remember that manual deploys don’t run your build command. For the fastest path to a live URL, use [Netlify Drop](https://app.netlify.com/drop) — a common misconception is that it only works with pre-built or static sites, but when you’re logged in it detects your framework and runs the build for you, so you can drop an unbuilt project just as easily as a built output folder.

-   **Git continuous deployment is the default answer.** Push to your production branch, Netlify builds and publishes. Pull requests get their own Deploy Preview automatically.
-   **Manual CLI deploys skip the build.** `netlify deploy` uploads what’s already on disk. Netlify Drop is the exception: when you’re logged in, it detects your framework and builds for you.
-   **Netlify Drop works with framework projects, not just static sites.** When logged in, it detects your framework and runs the build before publishing. Drop an unbuilt Next.js or Astro project the same way you’d drop a `dist` folder.
-   **Per-context settings live in `netlify.toml`.** Five predefined contexts (`production`, `deploy-preview`, `branch-deploy`, `preview-server`, `dev`), and any branch name works as a custom context.
-   **Add `.netlify` to `.gitignore` before you link.** Every linking path writes `.netlify/state.json`, and that file should never be committed.
-   **Never put secrets in `netlify.toml`.** It’s committed to your repo, and env vars declared there aren’t available to Functions at runtime anyway.
-   **A failed deploy never publishes.** Your previous deploy stays live, so there’s nothing to roll back. Fix forward.

## Deploying is the part that should stop being interesting

You’ve built the thing. Now you want it on the internet, and you want it to stay there without you thinking about it again. The frustrating part of most deploy setups isn’t the first push; it’s the third week, when someone else opens a pull request and nobody’s sure whether the preview URL reflects their branch or yesterday’s main.

Netlify has four ways to get code live. They’re not four options of equal weight competing for your attention. One of them is what you want almost always, and the other three exist for specific situations. Let’s sort out which is which, then look at the configuration that keeps deploys predictable once more than one person is pushing.

## When to use each deploy path

**Git continuous deployment.** Connect a repo through Git provider OAuth or the Netlify GitHub App. Netlify runs your build command on every push and deploys the result. Pushes to your production branch (`main` by default) become production deploys. Pull requests and merge requests get Deploy Previews. This is the right choice for any project with a repo behind it, which is nearly all of them.

**CLI deploys.** `netlify deploy` ships a directory from your machine. Use it when you have no Git connection, when you’re deploying build output produced by a pipeline Netlify isn’t running, or when you want a throwaway URL right now. The important constraint: **manual deploys do not run a build command.** Whatever is in the directory is what goes live.

**Netlify Drop.** Drag a folder to [app.netlify.com/drop](https://app.netlify.com/drop). Worth knowing what changed here, because a lot of writing about Drop is out of date: when you’re **logged in, Netlify detects your framework and builds before publishing.** You can drop an unbuilt project. A pre-built output folder works too. Only when you’re not logged in do files publish exactly as they are.

**Deploy to Netlify button.** For templates and starters you want other people to deploy. The template code has to live in a public GitHub.com or GitLab.com repo.

When you wouldn’t reach for any of these: if your question is really about which framework adapter to use or why your env var isn’t updating, that’s build configuration rather than deployment. And if you’re looking for a way to roll back to a previous deploy, see the failure modes section below. The short version is that the premise usually doesn’t hold.

## Worked example: Git deploys with per-context settings

Connect the repo once through the Netlify UI, then commit a `netlify.toml` at the repo root to control what happens in each context.

```
[context.production]  command = "make production"  [context.production.environment]    ACCESS_TOKEN = "super secret"  # Plugins context REQUIRES double brackets:  [[context.production.plugins]]    package = "@netlify/plugin-sitemap"
[context.deploy-preview.environment]  ACCESS_TOKEN = "not so secret"
[context.branch-deploy]  command = "make staging"
[context.dev.environment]  NODE_ENV = "development"
# Specific-branch context (overrides branch-deploy):[context.feature]  command = "make feature"
[context."features/branch"]  command = "gulp"
```

Two things to notice. The plugins block needs double brackets (`[[context.production.plugins]]`); single brackets fail. And a named branch context beats the general `branch-deploy` context, so `[context.feature]` wins for the `feature` branch.

Precedence runs: site globals, then context overrides. Production overrides globals when building production. More specific contexts override general ones. Only options you explicitly set get overridden, and file-based config beats UI settings.

For a one-off deploy from your machine:

```
netlify create              # new project from a natural-language promptnetlify deploy              # manual deploy, no continuous deploymentnetlify deploy --prod       # deploy directly to productionnetlify deploy --allow-anonymous   # temp deploy, claim within 1 hournpm update -g netlify-cli   # skew protection needs CLI v23.11.0+
```

An anonymous deploy creates a temporary project you can claim within one hour. On claim it takes on your team’s default visibility.

### Reading preview URLs

Once previews are running, the URL prefix tells you what you’re looking at:

-   Branch deploys: `<branch>--`
-   Pull request previews: `deploy-preview-<number>--`
-   Agent previews: `agent-<runID>--`
-   Permalinks: `<deployID>--`

While the first Deploy Preview is still building, its URL returns `Not Found`. That’s expected, not a broken link.

Branch deploys are **off by default.** A Developer or Owner turns them on under Project configuration > Build & deploy > Continuous Deployment > Branches and deploy contexts > Configure. You can add individual branches, use a wildcard prefix like `features/*`, or select **All**.

## Common failure modes

**You committed `.netlify/state.json`.** Every path that links or creates a project writes this file. Committing it means the next person to clone your repo inherits your project link, which is confusing at best. Add `.netlify` to `.gitignore` when you link, not after.

**You put a secret in `netlify.toml`.** That file is in your repo. On a public repo, so is your secret. There’s a second reason to avoid it that catches people even on private repos: **env vars declared in `netlify.toml` are available during the build, but not at runtime.** Functions, Edge Functions, and post-processing only see variables created through the UI, CLI, or API. So a secret in `netlify.toml` is both exposed and, for a Function, not even there at runtime.

**You ran `netlify deploy --prod` on a Git-connected site.** It works, and your deploy goes live. Then the next push to the production branch silently replaces it. If the hand-shipped deploy needs to stay up, lock the published deploy first.

**Your deploy failed and you want to roll back.** There’s nothing to roll back to. A failed deploy never publishes, so your previously published deploy is still serving traffic. Read the **“Why did it fail?”** diagnosis above the deploy log, revert the offending commit, and let CI deploy the fix. Retrying builds from branch HEAD rather than the original commit, so if HEAD has moved on, a retry builds the newer code.

**Secrets scanning failed your deploy.** Look at what got flagged before you reach for a config change. If it’s a real secret appearing in published output, that’s a leak: stop shipping it and rotate the value. Don’t silence the scanner. For a value that genuinely isn’t secret, scope the exception narrowly with `SECRETS_SCAN_OMIT_KEYS` or `SECRETS_SCAN_OMIT_PATHS`. Never `SECRETS_SCAN_ENABLED=false`.

**A directory in your publish folder has more than 54,000 files.** That fails the deploy. There’s no limit on total file count, only per directory.

**Skew protection isn’t working on your preview.** It’s production-context only by design. Branch deploys, Deploy Previews, and permalinks serve the latest deploy for their context. It also gets disabled if you password-protect production deploys, so protect non-production deploys only if you need both.

## Reference

### CLI commands

Command

What it does

`netlify login` / `netlify logout`

Browser OAuth. In CI, set `NETLIFY_AUTH_TOKEN` and `NETLIFY_SITE_ID` instead.

`netlify link`

Link the current directory to an existing project

`netlify link --git-remote-url <url>`

Link by Git remote

`netlify init` / `netlify init --manual`

Create and link a project, with or without Git CI/CD

`netlify unlink`

Unlink from the current project

`netlify sites:list`

List projects for the account

`netlify deploy`

Draft deploy, returns a preview URL

`netlify deploy --prod`

Deploy to production

`netlify deploy --dir=dist`

Deploy a specific directory

`netlify deploy --message="..."`

Add a deploy message

`netlify deploy:list`

List past deploys

`netlify build`

Run the build locally, mimicking the Netlify build environment

`netlify open:admin` / `netlify open:site`

Open the dashboard or the live site

Exit codes: `0` success, `1` general error, `2` auth error, `3` project not found, `4` build failed. Common flags: `--json`, `--silent`, `--debug`, `--force`.

### Deploy contexts

Context

When it applies

`production`

Pushes to the production branch

`deploy-preview`

Pull and merge request previews, plus agent runs

`branch-deploy`

Any branch with branch deploys enabled

`preview-server`

Preview server builds

`dev`

Local development

`<branch-name>`

Custom context matching that branch; overrides `branch-deploy`

### Deploy management

Action

Detail

Lock

**Lock to stop auto publishing** on the Deploys list. New deploys build but don’t publish.

Cancel

**Cancel deploy** on the in-progress deploy’s detail page

Retry

Builds from branch HEAD, not the original SHA

Download

Single file via **Deploy file browser**, or all files as ZIP from the header

Delete

Developer or Team Owner only. Can’t delete the published deploy or one in progress. Permanent.

Auto-cleanup

30 days, 90 on paid plans. Never the published deploy, the most recent successful production deploy, or the most recent successful branch deploy per branch. Enterprise can extend to 365 days.

### Skipping a deploy

Add `[skip ci]` or `[skip netlify]` to a pull request title to skip its Deploy Preview, or anywhere in a commit message to skip a branch or production deploy. On a multi-commit push, it has to be in the most recent commit. The next commit without the token deploys everything that was skipped.

### Deploy to Netlify button

```
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/netlify-statuskit)
```

Query parameters go in the URL; env vars go in the hash. `&fullConfiguration=true` adds a step for installing SDK extensions. `&base=blog` sets an alternate base directory for monorepos. `&create_from_path=examples/hello` clones only that subdirectory. `&branch=beta-feature` sets the production branch.

The `[template]` section in a root `netlify.toml` can declare `incoming-hooks` and `required-extensions`, and label env vars, but it **can’t set env var values** (use the URL hash) or a base directory (use `base`).

## FAQs

**How do I deploy a site to Netlify?** Connect your Git repository at [app.netlify.com/start](https://app.netlify.com/start). Netlify runs your build command and deploys on every push. For a deploy without Git, install the Netlify CLI and run `netlify deploy --prod`, or drag your folder to [app.netlify.com/drop](https://app.netlify.com/drop).

**Does `netlify deploy` run my build command?** No. Manual CLI deploys upload what’s already in the directory. Run your build first, or use Git continuous deployment so Netlify builds for you. Netlify Drop is the one exception: logged in, it detects your framework and builds before publishing.

**Can I deploy to Netlify without a build step?** Yes. Drop a folder at [app.netlify.com/drop](https://app.netlify.com/drop), or run `netlify deploy --dir=dist` against pre-built output.

**Why did my manual production deploy disappear?** If the project is connected to Git, the next push to the production branch replaces a hand-shipped deploy. Lock the published deploy if it needs to stay live.

**How do I roll back a Netlify deploy?** Usually you don’t need to. A failed deploy never publishes, so the previous deploy is still live. For a bad deploy that did publish, revert the commit and let CI deploy the fix. To stop new deploys from publishing while you sort it out, lock the published deploy.

**Why isn’t my Deploy Preview building for this branch?** Branch deploys are off by default. A Developer or Owner enables them under Project configuration > Build & deploy > Continuous Deployment > Branches and deploy contexts. Also, a Deploy Preview’s base branch has to be a production branch or a branch with branch deploys enabled.

**Why is my environment variable undefined in a Function?** If you declared it in `netlify.toml`, that’s why. Those variables aren’t available to the deploy environment. Set it through the Netlify UI, CLI, or API, make sure its scope includes Functions, and redeploy.

**How long does Netlify keep old deploys?** 30 days on the free plan, 90 on paid plans, adjustable up to 365 days on Enterprise. Netlify never auto-deletes the published deploy, the most recent successful production deploy, or the most recent successful branch deploy for each branch.

## Related

-   [`netlify.toml` file-based configuration](https://docs.netlify.com/build/configure-builds/file-based-configuration/)
-   [Build configuration](https://docs.netlify.com/configure-builds/)
-   [Netlify CLI: get started](https://docs.netlify.com/cli/get-started/)

This article is generated from Netlify’s open-source agent guidance at [netlify/context-and-tools](https://github.com/netlify/context-and-tools), the same reference our AI coding agents use.

* * *

Ready to put something live? Start at [netlify.new](https://netlify.new).