Skip to content

Prepare a Compose project

Tarsail deploys Docker Compose projects. It does not replace Compose, and it does not hide Compose behavior.

The safest way to adapt a project is to make the Compose file explicit: image tags, runtime env files, persistent volumes, health checks, and published ports should be clear before Tarsail enters the workflow.

Every service must have an image: value after docker compose config is evaluated.

services:
web:
build:
context: .
dockerfile: Dockerfile
image: my-app-web:${TARSAIL_RELEASE_ID:-local}

This works because:

  • local development can use my-app-web:local;
  • Tarsail deploys with TARSAIL_RELEASE_ID=<release-id>;
  • the remote Compose app uses the exact image tag loaded from the release bundle.

Before adding Tarsail, the project should build with normal Compose:

Terminal window
docker compose -f compose.yaml config
docker compose -f compose.yaml build
docker compose -f compose.yaml up -d
docker compose -f compose.yaml ps

Tarsail calls the same Docker Compose CLI. If Compose cannot build or run locally, Tarsail will not fix it.

Many projects need a separate production file:

compose.yaml
compose.production.yaml
tarsail.production.yml
.deploy/
production.env

In that case, point Tarsail at the production Compose file:

compose:
file: compose.production.yaml
env_file:
source: .deploy/production.env
target: shared/.env

Run:

Terminal window
tarsail --config tarsail.production.yml deploy

Do not hard-code secrets:

services:
web:
environment:
DATABASE_URL: postgres://real-user:[email protected]:5432/app

Use environment variables instead:

services:
web:
environment:
DATABASE_URL: ${DATABASE_URL}
SESSION_SECRET: ${SESSION_SECRET}

Store real values in an ignored file such as .deploy/production.env, then configure compose.env_file.

Tarsail does not configure TLS, DNS, Nginx, Caddy, firewalls, or cloud provider rules.

For a direct HTTP service:

services:
web:
image: my-app-web:${TARSAIL_RELEASE_ID:-local}
ports:
- "80:8080"

For a reverse proxy service:

services:
reverse-proxy:
image: nginx:1.27-alpine
ports:
- "443:443"
volumes:
- ./shared/certs:/etc/nginx/certs:ro
- ./files/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
web:
condition: service_healthy
web:
image: my-app-web:${TARSAIL_RELEASE_ID:-local}
expose:
- "8080"

In Tarsail releases, current/shared points to the remote shared/ directory, and current/files is part of the active release. Compose paths are resolved from the target path when Tarsail runs:

Terminal window
docker compose -p my-app --env-file current/.tarsail.env --env-file shared/.env -f current/compose.yaml up -d

Prefer paths that are clear from that command. For example:

volumes:
- ./current/files/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./shared/certs:/etc/nginx/certs:ro

or avoid host-path release files by baking config into an image.

Health checks make docker compose ps more useful:

services:
web:
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s

Tarsail does not currently wait for application-level health after docker compose up -d; it prints Compose status. Your Compose file should expose meaningful health state.

Be explicit about persistent state:

services:
db:
image: postgres:17-alpine
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:

Tarsail rollback does not restore db-data.

If your application runs migrations during deployment, write the Compose file so migration behavior is visible:

services:
migrate:
image: my-app-web:${TARSAIL_RELEASE_ID:-local}
command: ["my-app-migrate"]
restart: "no"
web:
image: my-app-web:${TARSAIL_RELEASE_ID:-local}
depends_on:
migrate:
condition: service_completed_successfully

Tarsail can bundle multiple Compose service images:

services:
api:
build: ./api
image: my-app-api:${TARSAIL_RELEASE_ID:-local}
worker:
build: ./worker
image: my-app-worker:${TARSAIL_RELEASE_ID:-local}
web:
build: ./web
image: my-app-web:${TARSAIL_RELEASE_ID:-local}

Each service name becomes an image tar file name under images/. Service names may use letters, numbers, dot, underscore, and hyphen.

Tarsail requires every image to exist locally by the time it creates the bundle.

For services without a build: section, pull or build the image locally first:

Terminal window
docker pull nginx:1.27-alpine
tarsail deploy

If an image is missing locally during the bundle step, Tarsail fails before uploading a partial release.

Use files for non-secret release-owned files:

files:
- source: deploy/nginx
target: files/nginx

The local source may be a file or directory. It must not be a symlink. Directories must not contain symlinks.

Targets must be under files/.

my-app/
Dockerfile
compose.yaml
tarsail.yml
.deploy/
production.env
app.key
deploy/
nginx/
default.conf

Commit:

  • Dockerfile;
  • compose.yaml;
  • tarsail.yml when it contains no private infrastructure details;
  • safe files under deploy/;
  • .deploy/.gitignore or .env.example placeholders.

Do not commit:

  • real .deploy/*.env;
  • private keys;
  • production certificates;
  • database passwords;
  • tokens;
  • private server inventory.