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.
Required Compose shape
Section titled “Required Compose shape”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.
Build locally first
Section titled “Build locally first”Before adding Tarsail, the project should build with normal Compose:
docker compose -f compose.yaml configdocker compose -f compose.yaml builddocker compose -f compose.yaml up -ddocker compose -f compose.yaml psTarsail calls the same Docker Compose CLI. If Compose cannot build or run locally, Tarsail will not fix it.
Use a production Compose file when needed
Section titled “Use a production Compose file when needed”Many projects need a separate production file:
compose.yamlcompose.production.yamltarsail.production.yml.deploy/ production.envIn that case, point Tarsail at the production Compose file:
compose: file: compose.production.yaml env_file: source: .deploy/production.env target: shared/.envRun:
tarsail --config tarsail.production.yml deployKeep secrets out of the Compose file
Section titled “Keep secrets out of the Compose file”Do not hard-code secrets:
services: web: environment: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.
Ports and reverse proxies
Section titled “Ports and reverse proxies”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:
docker compose -p my-app --env-file current/.tarsail.env --env-file shared/.env -f current/compose.yaml up -dPrefer 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:roor avoid host-path release files by baking config into an image.
Health checks
Section titled “Health checks”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: 10sTarsail 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.
Databases and volumes
Section titled “Databases and volumes”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_successfullyMulti-image projects
Section titled “Multi-image projects”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.
Prebuilt images
Section titled “Prebuilt images”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:
docker pull nginx:1.27-alpinetarsail deployIf an image is missing locally during the bundle step, Tarsail fails before uploading a partial release.
Files copied into releases
Section titled “Files copied into releases”Use files for non-secret release-owned files:
files: - source: deploy/nginx target: files/nginxThe local source may be a file or directory. It must not be a symlink. Directories must not contain symlinks.
Targets must be under files/.
Suggested project layout
Section titled “Suggested project layout”my-app/ Dockerfile compose.yaml tarsail.yml .deploy/ production.env app.key deploy/ nginx/ default.confCommit:
Dockerfile;compose.yaml;tarsail.ymlwhen it contains no private infrastructure details;- safe files under
deploy/; .deploy/.gitignoreor.env.exampleplaceholders.
Do not commit:
- real
.deploy/*.env; - private keys;
- production certificates;
- database passwords;
- tokens;
- private server inventory.