---
title: Using a Dockerfile
description: Deploy a project that already has its own Dockerfile, and what ZevCloud does differently when it finds one.
---

If your repository already has a `Dockerfile`, ZevCloud uses it as-is. You've described your build; we don't second-guess it.

## What happens automatically

When you create a service from a repository, we look for a `Dockerfile` in your **Root Directory**. If one is there:

- The **Build Pack** is set to Dockerfile for you.
- The **Port** is filled in from the file's `EXPOSE` line, when it has one.
- Your install and build commands are ignored — your Dockerfile is the whole build.

`dockerfile` (lowercase) and `Containerfile` are recognised too. We only look in the Root Directory, not in subfolders.

> **Note: Automatic detection versus your Dockerfile**
>
> Without a Dockerfile, ZevCloud detects your stack and builds it for you. With one, none of that applies — the packages installed, the base image, and the runtime version all come from your file. Changing the Runtime Version setting has no effect on a Dockerfile build, which is why the picker is hidden for them.

## Requirements

**Bind to `$PORT`.** ZevCloud sets a `PORT` environment variable on every service. Reading it is the most reliable option, because then the port can never drift out of sync with your service settings:

```dockerfile
ENV PORT=8000
EXPOSE 8000
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}"]
```

If you'd rather hard-code a port, make sure `EXPOSE`, your `CMD`, and the **Port** in Service Settings all agree. A mismatch produces a Bad Gateway on a container that's running perfectly.

**Listen on `0.0.0.0`, not `127.0.0.1`.** An app bound to localhost inside a container is unreachable from outside it. This is the second most common cause of a healthy container that serves nothing.

**Build for `linux/amd64`.** If you build and test locally on an Apple Silicon Mac, use `docker build --platform linux/amd64` to catch architecture problems before deploying rather than after.

## Environment variables

Variables you set in the **Variables** tab are available as build arguments and at runtime. To use one during the build, declare it:

```dockerfile
ARG DATABASE_URL
ENV DATABASE_URL=$DATABASE_URL
```

> **Warning: Don't bake secrets into the image**
>
> Anything written into an image layer stays in that layer, including values you later overwrite. Keep secrets to runtime: set them in the Variables tab and read them with `os.environ` / `process.env` when the app starts, rather than at build time.

## Writing one that builds fast

Docker reuses layers until one changes, and every layer after that point is rebuilt. So the order of your instructions decides how much work each deploy does.

Copy your dependency manifest first, install, and only then copy your source. That way a code change reuses the install layer:

```dockerfile
# Dependencies: only re-runs when the lockfile changes
COPY package*.json ./
RUN npm ci

# Source: changes on every commit, so it goes last
COPY . .
RUN npm run build
```

The mistake to avoid is `COPY . .` before the install step, which makes every commit reinstall every dependency.

Add a `.dockerignore` so `node_modules`, `.git`, and build output aren't shipped into the build:

```
node_modules
.git
dist
.env
```

A multi-stage build keeps compilers and dev dependencies out of the final image, which makes deploys faster and the running container smaller:

```dockerfile
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/main.js"]
```

## Compose files

ZevCloud deploys one container per service, so `docker-compose.yml` isn't used as a build input. If your compose file describes several containers, create one ZevCloud service per container and point each at the folder holding its Dockerfile.

For the things compose usually provides alongside your app:

- **Databases** — create a [managed database](/guide/databases) and connect with the connection string it gives you.
- **Object storage** — use [storage buckets](/guide/storage).
- **Shared networking** — services in the same project reach each other over the internal network.

## Switching between build modes

**Service Settings → Build & Deploy → Build Pack** lets you switch at any time. Changing to Dockerfile requires a `Dockerfile` in your Root Directory; if there isn't one, the change is rejected rather than silently falling back, so you don't end up with a build that quietly ignores the setting.

Either way the change applies on the next deploy.
