Skip to content

Deploy Basics

The most straightforward deployment: SSH to your server, pull the new image, and start a container.

Terminal window
# On the deployment server
docker pull acme/api-server:2.1.0
docker stop api-server || true
docker rm api-server || true
docker run -d \
--name api-server \
--restart unless-stopped \
-p 3000:3000 \
-e DATABASE_URL="$DATABASE_URL" \
acme/api-server:2.1.0

The || true prevents the pipeline from failing if the container does not yet exist on a first deploy.

The --restart flag tells Docker what to do if the container exits:

PolicyBehaviour
no (default)Never restart
on-failureRestart only on non-zero exit
alwaysAlways restart, including after docker restart daemon
unless-stoppedLike always but does not restart if you explicitly docker stop it

For production services, use unless-stopped. It survives host reboots and daemon restarts but respects intentional stops.

If your server already runs services with Compose v2, updates are a two-command operation:

Terminal window
# Pull all updated images defined in compose.yml
docker compose pull
# Recreate containers that have a newer image
docker compose up -d

Compose recreates only the services whose image digest has changed, leaving unchanged services running. If you use a compose.yml with pinned image tags:

services:
api:
image: acme/api-server:2.1.0
restart: unless-stopped
ports:
- "3000:3000"
env_file:
- .env

Change the tag in compose.yml and run docker compose up -d — Compose detects the tag change and recreates the service.

Environment variables and secrets on the host

Section titled “Environment variables and secrets on the host”

Never bake secrets into your image. Instead, pass them at runtime.

Three patterns, from simplest to most robust:

Terminal window
docker run -d \
-e DATABASE_URL="postgres://user:pass@db:5432/app" \
-e SECRET_KEY="abc123" \
acme/api-server:2.1.0

Simple, but the values appear in docker inspect output and shell history.

Terminal window
# .env (not committed to git)
DATABASE_URL=postgres://user:pass@db:5432/app
SECRET_KEY=abc123
Terminal window
docker run -d --env-file .env acme/api-server:2.1.0

Cleaner than -e flags. Keep the .env file in a secrets manager or vault on the server.

For teams running Docker Swarm or Compose with the secrets: block, secrets are mounted as files at /run/secrets/<name> inside the container. This is the most secure option — secrets are never exposed as environment variables.

services:
api:
image: acme/api-server:2.1.0
secrets:
- db_password
secrets:
db_password:
external: true

After pulling and restarting, confirm the new version is running:

Terminal window
docker ps
docker logs api-server --tail 20
docker inspect api-server | grep Image

Hands-on: pull and run a public image with a restart policy

Section titled “Hands-on: pull and run a public image with a restart policy”

The snippet below demonstrates docker pull, docker run -d --restart unless-stopped, and log inspection — using a public image so no credentials are needed.

# Pull a versioned nginx image
docker pull nginx:1.27-alpine

# Run it detached with a restart policy
docker run -d \
  --name web \
  --restart unless-stopped \
  -p 8080:80 \
  nginx:1.27-alpine

# Verify it is running
docker ps

# Check logs
docker logs web --tail 5

# Simulate an update: pull a different tag and recreate
docker pull nginx:1.26-alpine
docker stop web && docker rm web
docker run -d \
  --name web \
  --restart unless-stopped \
  -p 8080:80 \
  nginx:1.26-alpine

docker ps
Which restart policy restarts a container on host reboot but does NOT restart it if you explicitly stop it with docker stop?
What does `docker compose up -d` do when you update the image tag in compose.yml?
Why should secrets never be baked into a Docker image?
Which flag makes a running container survive a Docker daemon or host restart?