# Deploy an app

> Build a container from a Dockerfile, run it in its own microVM, and reach it over HTTPS

URL: https://pier.run/docs/guides/deploy-an-app

**Goal.** A repository with a `Dockerfile` becomes a running app with a public HTTPS
hostname. The example is `acme`'s `api`: a Go HTTP server in `api/`, listening on 8080.
For an image that already exists (no build), see [Variations](#variations).

### Declare the app

```yaml title="pier.yaml"
version: 1
name: acme
project: acme-ad19
environments:
  prod:
    apps:
      - name: api
        build_context: ./api      # ./api/Dockerfile
        targets:
          - name: primary
            plan: app-s
```

`build_context` is the directory `docker build` runs in, relative to `pier.yaml`; the
Dockerfile is read from there (`dockerfile:` on the target overrides the path). `plan` is
the target's size ([Plans](/docs/plans)). `project:` is the slug the platform generated
when the project was created; `pier config init --name acme` writes it.

### Deploy

```bash
pier deploy
```

```text title="output"
→  Comparing prod environment against platform state...

  Changes:
    + create environment "prod" (new environment)
    + create app "api" in prod (plan: app-s, replicas: 1)
    + create postgres "main" in prod (plan: pg-s, version: 17, replicas: 1)
    + create bucket "uploads" in prod (access: private)
    + create site "www" in prod

  Deploy:
    app "api"    build (first deploy)
    site "www"    build required (new resource — initial build required)
    postgres "main"    deploy (deployed inline)
    bucket "uploads"    deploy (deployed inline)

OK  Applied 5 config change(s) to prod
→  Building api from ./api
→  Building pier-build/api:latest...

→  Pushing registry.pier.run/01a05313-e4fa-7707-a014-05f1ae901cec/api:9eed0df...
9eed0df: digest: sha256:13e134f00256cce6b3bc076b2671fb63dca2eee0bfb14509576612703a39c662 size: 855
OK  Pushed registry.pier.run/01a05313-e4fa-7707-a014-05f1ae901cec/api:9eed0df
→  Deploying api...
→  Found 1 site files to deploy
→  Uploading 1 files...
OK  All files uploaded
→  Waiting for infrastructure provisioning...
OK  Infrastructure ready

OK  Summary: 1 built, 4 deployed

```

`deploy` first converges the whole environment's declaration; the `create …` lines are
the `pier apply` step that `deploy` implies, so `acme`'s Postgres, bucket and site are
provisioned in the same run. It then builds the image with the local docker, pushes it to
the project's registry with the credentials `pier login` installed, and rolls the target.
A new app reaches `running` when its first instance passes its readiness check.

### Reach it

```bash
pier info api
```

```text title="output"
Name         api
ID           app_01m19h7sdyef8v937vjvbjw7hj
Environment  prod
Target       primary
Deployment   depl_01m19h7sefeemtmtcc334e1wax
Status       running
Replicas     1
Image        registry.pier.run/01a05313-e4fa-7707-a014-05f1ae901cec/api:9eed0df
Port         8080
URL          https://9z2vay2eae6.app.pier.run
CPU          500m
Memory       512Mi
Created      2026-08-30 22:30:08
Updated      2026-08-30 22:30:08

                                         
 INSTANCE PHASE   READY RESTARTS STARTED 
                                         
 j675p    Running yes   0        42s ago 
                                         

```

```bash
curl -sS https://s9vmcq9byrx.app.pier.run/
curl -sS https://s9vmcq9byrx.app.pier.run/json
```

```text title="output"
hello from app-01a05313-e5c2-70df-acd5-6de1595d13b8-666765455b-j675p
{"host":"app-01a05313-e5c2-70df-acd5-6de1595d13b8-666765455b-j675p","status":"ok","version":"1.0.0"}

```

The hostname is keyed by a random id, not by the app's name, so renaming `api` later
does not change it. A [custom domain](/docs/guides/add-a-custom-domain) attaches your own name.

### Watch it

```bash
pier logs api --tail 5
```

```text title="output"
  api · the last hour
2026-08-30 22:31:46.362  INFO     [t5dk7]  2026/08/30 14:31:46 [tick 8] hello-web running on app-01a05313-e5c2-70df-acd5-6de1595d13b8-58946dd54b-t5dk7
2026-08-30 22:31:51.366  INFO     [t5dk7]  2026/08/30 14:31:51 [tick 9] hello-web running on app-01a05313-e5c2-70df-acd5-6de1595d13b8-58946dd54b-t5dk7
2026-08-30 22:31:56.366  INFO     [t5dk7]  2026/08/30 14:31:56 [tick 10] hello-web running on app-01a05313-e5c2-70df-acd5-6de1595d13b8-58946dd54b-t5dk7
2026-08-30 22:32:01.370  INFO     [t5dk7]  2026/08/30 14:32:01 [tick 11] hello-web running on app-01a05313-e5c2-70df-acd5-6de1595d13b8-58946dd54b-t5dk7
2026-08-30 22:32:06.374  INFO     [t5dk7]  2026/08/30 14:32:06 [tick 12] hello-web running on app-01a05313-e5c2-70df-acd5-6de1595d13b8-58946dd54b-t5dk7

```

`pier logs api --follow` streams; `pier monitor api` shows CPU, memory and requests;
`pier events` lists what the platform did.

## What changed

The declaration above is the whole change. `pier deploy` recorded a deploy for `api`
(`pier deploys ls api`) with the digest of the image it built; a
[rollback](/docs/guides/roll-back-a-deploy) returns to that record.

## Variations

* **A pre-built image, no build**: set `image:` and `port:` on the target instead of a
  `build_context`. `pier deploy` pulls the image and does not run docker.

  ```yaml title="pier.yaml"
      apps:
        - name: whoami
          targets:
            - name: primary
              image: traefik/whoami:v1.10.1
              port: 80
              plan: app-s
  ```

* **No Dockerfile**: Pier detects Node, Python, Go, Rust, Ruby, PHP, Java, .NET, Elixir
  and Deno projects and builds them without one. Add a `Dockerfile` to control the build.

* **From the CLI instead of the file**: `pier add app --name api --plan app-s` declares
  the same thing and writes it to `pier.yaml`; `--stage` writes without applying.

* **Build on the platform on each push**: [Deploy from GitHub](/docs/guides/deploy-from-github).

## See also

[Apps](/docs/resources/apps) · [`pier deploy`](/docs/cli/reference/deploy) ·
[`pier info`](/docs/cli/reference/info) · [Concepts: target](/docs/concepts#target)
