---
title: "Publish and Update Sites with the Netlify CLI"
description: "Use the Netlify CLI to publish a built site non-interactively, capture its live URL, and deploy later revisions to the same address."
source: "https://www.netlify.com/knowledge-base/publish-and-update-sites-with-the-netlify-cli/"
last_updated: "2026-07-24T12:54:57.000Z"
---
An agent that writes code can also ship it. The Netlify CLI turns any built directory — a `dist/` folder, a single `index.html`, a static export — into a live URL with one command, and every later revision goes to the same URL with the same command. This guide is written for that workflow: non-interactive, scriptable, and safe to run in a loop.

This guide is CLI-only. For programmatic integration from a backend service, see the [Netlify API](https://docs.netlify.com/api/get-started/) instead. Commands and flags below were checked against Netlify CLI v26.1.0; run `netlify --version` and consult the [command reference](https://cli.netlify.com/) if yours differs.

## What “an artifact” means here

Anything you have already built into static files:

-   a framework build output (`dist/`, `build/`, `out/`)
-   a single `index.html` and its assets
-   a static export from any tool

The CLI uploads finished files. It can run your build first, or skip the build entirely and upload a folder as-is — which is the common case for an agent that has already produced the output.

## Authenticate without a browser

Interactive login (`netlify login`) opens a browser, which an agent cannot do. Instead, generate a personal access token once (Netlify UI → **User settings → Applications → New access token**) and pass it as an environment variable:

```
export NETLIFY_AUTH_TOKEN=your_token_value
```

Every CLI command reads this variable and authenticates silently ([Netlify CLI: get started](https://docs.netlify.com/cli/get-started/)). Store it as a secret in whatever runs the agent; never hard-code it, and scope it as narrowly as your setup allows rather than reusing a full-access personal token across many agents. If you’d rather pass the token per command than set it in the environment, every command also accepts `--auth $NETLIFY_AUTH_TOKEN`.

## Publish a throwaway artifact with no account

For a one-off — an artifact you just want a shareable URL for, with no site to manage and no token — deploy anonymously:

```
netlify deploy --dir=dist --no-build --allow-anonymous --json
```

This uploads the folder and returns a **claimable** URL: the site exists without an account. The deploy output includes a site ID and a drop token; a human claims the site within **one hour** by running `netlify claim --site <site-id> --token <drop-token>` (or by logging in). **Unclaimed sites are removed after one hour**, so this is for disposable previews only — use the authenticated flow below for anything you’ll revise or keep ([deploy command reference](https://cli.netlify.com/commands/deploy/)).

## Create a site to publish to

A deploy needs a target site. Creating one interactively prompts for a team and name — an agent should create it explicitly instead:

```
netlify sites:create --name my-project --account-slug your-team-slug
```

`--name` sets the subdomain (`my-project.netlify.app`); `--account-slug` picks the team to create it under ([sites:create reference](https://cli.netlify.com/commands/sites/)). Find your team slug with `netlify teams:list` or in the Netlify UI under Team settings — note that the CLI calls a team an “account” in some flags (`--account-slug` here, `--team` on `deploy`), so the two names refer to the same thing. If the name is taken, the command reports it — pick another rather than retrying the same one.

Alternatively, create the site and deploy in a single command: `netlify deploy` accepts `--site-name <name>` (which implies site creation if it doesn’t exist) together with `--team <slug>`, so `netlify deploy --site-name my-project --team your-team-slug --dir=dist --no-build --prod` does both at once.

To reuse an existing site instead of making a new one, link the current folder to it:

```
netlify link
```

This writes a `siteId` into `.netlify/state.json` in the project folder, so subsequent deploys from that folder target the linked site with no further flags ([Netlify CLI: get started](https://docs.netlify.com/cli/get-started/)).

**In a stateless environment** — a fresh container per run, where `.netlify/state.json` won’t survive — don’t rely on linking. Capture the site ID once from `sites:create --json` (the `id`/`site_id` field), persist it wherever the agent keeps state, and pass `--site <site-id>` explicitly on every deploy. That makes each deploy self-contained and independent of the working directory.

## First publish

Two things matter to an agent here: which URL you get, and getting the URL back in a form you can parse.

**Deploy a prebuilt folder to a draft URL** (a unique, unlisted preview address):

```
netlify deploy --dir=dist --no-build --json
```

-   `--dir=dist` uploads that folder as the site.
-   `--no-build` skips the build — use it when you have already built the output, which is the usual agent case ([deploy command reference](https://cli.netlify.com/commands/deploy/)).
-   `--json` prints the deploy result as JSON so you can read the URL programmatically instead of scraping human-readable stdout.

The JSON includes `deploy_url` (the draft address) and, on a production deploy, `url` (the live production address). Parse that field rather than regexing the console text.

**Promote the same output to the production URL** with `--prod`:

```
netlify deploy --dir=dist --no-build --prod --json
```

This publishes to the site’s stable production URL (`my-project.netlify.app` or a custom domain). The draft-first flow is useful when you want to verify a build at its preview URL before promoting it; if you just want it live, deploy straight to `--prod`.

If you do want Netlify to run the build for you (it has a repo or a build command configured), drop `--no-build` and the CLI runs the build before uploading.

## Capture the live URL

Because `--json` emits structured output, an agent reads the URL directly:

```
URL=$(netlify deploy --dir=dist --no-build --prod --json | jq -r '.url // .deploy_url')echo "Live at: $URL"
```

The `.url // .deploy_url` fallback keeps this robust across deploy types: `deploy_url` (the draft address) is always present, while `url` (the production address) appears on a production deploy. Hand `$URL` back to the user. Do not parse the decorated stdout banner — it is formatted for humans and its layout is not a stable contract.

## The revision loop: updating a published site

This is the part an agent repeats. To ship a revision, rebuild your output and run the **same deploy command** against the **same site**:

```
# after editing and rebuilding into dist/netlify deploy --dir=dist --no-build --prod --json
```

Three properties make this safe to run repeatedly:

-   **The production URL is stable.** Every production deploy replaces what’s live at the same address. The user’s link never changes across revisions.
-   **Deploys are atomic and immutable.** Each deploy is a complete, versioned snapshot. A new deploy goes live all at once; it does not patch files in place. Nothing serves a half-updated site mid-deploy.
-   **Rollback is instant.** Because every deploy is retained as an immutable snapshot, you can revert to any previous one from the Netlify UI (**Deploys → select a deploy → Publish deploy**) without rebuilding. An agent that ships a bad revision can tell the user exactly how to roll back.

So the mental model is: _build → deploy → same URL, new immutable snapshot._ Repeat as many times as the task needs.

**One cost note for loops:** on credit-based plans each **production** deploy costs 15 credits, but **draft and preview deploys are free** ([how credits work](https://docs.netlify.com/manage/accounts-and-billing/billing/billing-for-credit-based-plans/how-credits-work/)). So an agent iterating rapidly should deploy to draft URLs (omit `--prod`, see the next section) and promote to production only when a revision is ready — that keeps iteration free and avoids burning credits on every intermediate build.

## Preview a revision before it goes live

When you want the user to review a change without touching the production URL, deploy to a named draft instead of production:

```
netlify deploy --dir=dist --no-build --alias=review-42 --json
```

`--alias` fixes the subdomain of the draft URL, so you get a predictable preview address (useful when you want the same review link across a few iterations) rather than a random one ([deploy command reference](https://cli.netlify.com/commands/deploy/)). Alias strings are capped at 37 characters. Omit `--prod` and the production URL stays untouched until you promote a build to it.

## A complete agent recipe

End to end, from nothing to a live URL and one revision:

```
# 0. Auth (once, from a stored secret)export NETLIFY_AUTH_TOKEN=your_token_value
# 1. Create the site (once)netlify sites:create --name my-project --account-slug your-team-slug
# 2. Link this folder to it so later deploys need no --site flagnetlify link --name my-project
# 3. Build your output however the project builds (agent's job)npm run build            # produces dist/
# 4. Publish to production and capture the URLURL=$(netlify deploy --dir=dist --no-build --prod --json | jq -r '.url')echo "Live at: $URL"
# 5. Later: edit, rebuild, redeploy — same command, same URLnpm run buildnetlify deploy --dir=dist --no-build --prod --json | jq -r '.url'
```

For a single HTML file with no build step, point `--dir` at the folder that contains it and skip step 3 entirely.

## Common gotchas for automated runs

**Deploying the wrong folder.** `--dir` must point at the finished output (`dist`, `build`, `out`), not the project root. Uploading the root serves your source files, not your site.

**Forgetting `--no-build` on a prebuilt folder.** Without it, the CLI may try to run a build command. If the output is already built, `--no-build` keeps the deploy fast and predictable.

**Relying on the interactive prompt.** A first `netlify deploy` with no linked site prompts the human to pick or create one — which stalls an agent. Create the site with `sites:create` and `link` it (or pass `--site`) so nothing ever blocks on input.

**Parsing stdout instead of `--json`.** The human-readable output is not a stable interface. Always add `--json` and read the field you need.

**Missing `NETLIFY_AUTH_TOKEN`.** Without the token in the environment, commands fall back to interactive login and hang. Confirm the variable is set before the first command.

## Resources

-   [Netlify CLI: get started](https://docs.netlify.com/cli/get-started/)
-   [`netlify deploy` command reference](https://cli.netlify.com/commands/deploy/)
-   [`netlify sites:create` command reference](https://cli.netlify.com/commands/sites/)
-   [Netlify CLI: create deploys](https://docs.netlify.com/cli/get-started/#deploy-to-a-globally-unique-url)
-   [Personal access tokens](https://docs.netlify.com/api/get-started/#authentication)