Dockship is a small Go CLI for moving locally built Docker images directly to one remote Docker host and starting them with Docker Compose. It is intended for internal deployments where operating a registry would add more machinery than value.
Dockship is not a container registry and does not implement Docker, SSH, Git, or compression protocols. It orchestrates the Docker, SSH, Git, and zstd command-line tools already installed on the machine.
It builds multiple images with bounded concurrency, streams one docker image save archive through zstd and OpenSSH without writing a tar file, writes a Compose environment file, runs Compose, and performs local HTTP health checks. The same operational commands are available through an embedded local web panel. It does not provide Kubernetes support, remote builds, secret storage, image pruning, or automatic rollback.
- The root package is the public install entrypoint;
cmd/dockshipremains as a compatibility entrypoint. Both delegate signal handling and process exit codes tointernal/entrypoint. internal/clidefines Cobra commands and user-facing workflows.internal/configstrictly decodes YAML, applies explicit defaults, resolves local paths relative to the config file, and aggregates validation errors.internal/deploymentcreates side-effect-free plans and orchestrates builds, streaming transfer, Compose, environment files, and health checks.internal/processcentralizes local command execution and bounded stderr capture.internal/webpanelserves the local control deck, serializes command execution, streams logs with server-sent events, and keeps non-secret event summaries in the user cache.uiandwebcontain vendored GSXUI components and embedded production assets. The panel is server-rendered and ships inside the Dockship binary; it is not a separate frontend service.internal/shellis the only place that quotes dynamic POSIX shell values.internal/sshaskpassprovides Dockship's dependency-free OpenSSH askpass helper mode.internal/apperrormaps typed failures to stable exit codes.
Local commands never use a shell. The remote image-load pipeline is static; dynamic remote paths are single-quoted with tested POSIX escaping.
- Go 1.26.1 or newer to build Dockship
- Docker CLI and a working local Docker daemon
- OpenSSH
ssh zstd- Git is optional; without it Dockship uses a UTC timestamp as the deployment ID
- A Linux/Unix remote host with Docker and Docker Compose v2
- SSH keys/agent/configuration are preferred; password authentication is also supported
Dockship checks executable presence with PATH lookup. It does not change host-key policy, run sudo, or modify either Docker daemon.
go install github.com/vahidid/dockship@latestOr build a repository-local binary:
go build -o bin/dockship .dockship init
dockship config validate
dockship deploy production --dry-run
dockship build production
dockship deploy production
dockship deploy production --image frontend
dockship status production
dockship webThe default configuration is ./dockship.yaml; use --config path/to/file.yaml to select another file. Local paths are relative to the configuration file, not the invoking working directory. dockship init --force is required to overwrite an existing file.
dockship webThe command starts the embedded panel on http://127.0.0.1:7337 and opens the default browser. It can start before dockship.yaml exists, or when the selected configuration is invalid. In that case, the panel opens a four-step setup flow for defining the project, build images, deployment targets, and reviewing the generated YAML. An invalid existing file is backed up before the validated replacement is written.
Use --no-open to start it without opening a browser, or choose another loopback port:
dockship web --no-open --listen 127.0.0.1:8088The panel can:
- initialize and validate a configuration without running
dockship init; - queue and cancel
deploy,build,status, andconfig validateoperations; - stream stdout and stderr from the selected operation in real time;
- show configured targets, configured image repositories, their newest local Docker tags, and recent command runs;
- retain non-secret event summaries under the operating system's user cache directory.
Commands are serialized so two deployments cannot race each other. The HTTP server only accepts loopback listen addresses, mutating API calls require an in-memory session token, and cross-origin command requests are rejected. For access from another machine, keep Dockship bound to loopback and use an SSH tunnel.
The web interface is rendered with vendored GSXUI components. Production CSS and JavaScript are embedded in the Go binary, so Node.js is not required to run the panel. Contributors only need Node.js when changing panel styles:
go tool gsx generate
npm install
npm run build:webInteractive terminal password prompts are intentionally unavailable to browser-started commands. Use an SSH key, ssh_password_env, DOCKSHIP_SSH_PASSWORD, or the existing plaintext configuration fallback.
See dockship.example.yaml for a commented multi-image example. Unknown YAML fields are rejected.
version: 1
project: example
build:
concurrency: 2
transfer:
compression: zstd
compression_level: 3
images:
frontend:
image: example-frontend
context: ./frontend
dockerfile: ./frontend/Dockerfile
platform: linux/amd64
compose_env: FRONTEND_IMAGE
backend:
image: example-backend
context: ./backend
dockerfile: ./backend/Dockerfile
target: production
compose_env: BACKEND_IMAGE
build_args:
APP_MODE: web
labels:
app.component: backend
targets:
production:
host: deploy@example.com
# Recommended password fallback:
# ssh_password_env: PRODUCTION_SSH_PASSWORD
# Plaintext alternative, discouraged:
# ssh_password: change-me
compose_dir: /opt/example
compose_file: docker-compose.yml
# Existing files on the remote host. Dockship never rewrites these.
compose_env_files:
- .env.production
# Generated image mappings only. Dockship owns this file.
dockship_env_file: .dockship.env
images: [frontend, backend]
healthchecks:
- name: application
url: https://example.com/health
retries: 10
interval_seconds: 3
timeout_seconds: 5build.concurrency defaults to 1. Compression defaults to zstd level 3. Health checks default to 10 retries, a 3-second interval, and a 5-second request timeout when those fields are omitted.
Authentication uses the first available non-empty source in this order:
- The environment variable named by the target's
ssh_password_env. - The global
DOCKSHIP_SSH_PASSWORDenvironment variable. - The target's plaintext
ssh_passwordvalue. - An OpenSSH key or SSH agent, checked with
BatchMode=yes. - If key/agent authentication is rejected, a no-echo password prompt on the local terminal.
Examples:
export DOCKSHIP_SSH_PASSWORD='your-password'
dockship deploy productionOr use a target-specific variable without putting the password in YAML:
targets:
production:
host: deploy@example.com
ssh_password_env: PRODUCTION_SSH_PASSWORDConfigured and prompted passwords use OpenSSH's standard SSH_ASKPASS mechanism. Dockship invokes its own executable as the askpass helper, so no password helper or SSH library is required. Passwords never appear as command-line arguments and are excluded from normal and verbose output. A non-interactive environment without working SSH keys must supply one of the environment/configuration sources because Dockship cannot show a terminal prompt there.
Targets reference globally defined image configuration names. Every image in one deployment receives the same tag, is built with the configured concurrency limit, and is included in one archive stream. Repeated --image flags select a subset while preserving target order:
dockship deploy production --image frontend --image backendOnly selected image mappings are updated. Dockship atomically merges them into dockship_env_file, preserving mappings for unselected target images. A target must receive one complete deployment before partial deployments can preserve previous mappings. Compose still evaluates its complete configuration and may inspect or recreate services outside that subset; Dockship does not translate image selection into Compose service selection in version one.
Create the configured Compose directory and file on the remote host before deploying. Images must be variable-driven:
services:
frontend:
image: ${FRONTEND_IMAGE}
env_file:
- .env.production
backend:
image: ${BACKEND_IMAGE}Application environment files belong on the remote host under compose_dir and are listed in compose_env_files. Dockship treats them as read-only; provision them manually or through your existing secret/configuration system. dockship_env_file is separate and contains only DOCKSHIP_DEPLOYMENT_ID plus generated image tags.
Dockship passes application files first and its generated override last:
docker compose \
--file docker-compose.yml \
--env-file .env.production \
--env-file .dockship.env \
up -dLater Compose env files override earlier files, so Dockship controls image variables without touching application values. A service-level env_file passes variables into the container, while CLI --env-file supplies Compose interpolation such as ${DATABASE_URL} and ${FRONTEND_IMAGE}; use both when the application needs both behaviors.
Updates to dockship_env_file are written through permission-restricted temporary files and an atomic rename. Dockship restores its previous mapping if deployment fails before Compose completes, unless --keep-remote-env is set. After Compose succeeds, the new mapping is retained even if a health check fails because automatic service rollback is not implemented.
Migration from older configurations: replace remote_env_file: .dockship.env with dockship_env_file: .dockship.env, then add any existing application env files under compose_env_files. The old field is rejected by strict configuration decoding.
The default deployment ID is git rev-parse --short HEAD. If Git is unavailable or the directory is not a repository, Dockship uses a timestamp such as 20260721-184530. Override it with a valid Docker tag:
dockship deploy production --tag release-2026-07-21With --skip-build, Dockship first prefers the generated Git tag when it exists for every selected image. Otherwise it automatically selects the newest local tag shared by all selected image repositories. An explicit --tag remains strict and must exist for every selected image. Dockship verifies the resolved tags before transfer.
dockship --verbose deploy production --dry-runDry-run parses and validates configuration, may query Git, creates the complete plan, and renders commands. It does not look up or execute Docker, SSH, zstd, Compose, or health checks and does not write files. Secret-like build-argument values are masked in rendered commands.
Checks run sequentially from the local machine after Compose succeeds. HTTP 200–399 is successful. Each check has its own timeout and respects SIGINT/SIGTERM cancellation. Exhausted retries fail the deployment with exit code 9; loaded images and running services remain in place because rollback is not implemented. Use --no-healthcheck to skip them.
Treat the YAML as executable deployment input even though Dockship validates it. Remote paths are safely POSIX-quoted, but operators should still review configuration changes. Dockship relies on normal OpenSSH host-key checking, invokes no sudo, and never disables host-key verification.
Prefer SSH keys or ssh_password_env. A value in ssh_password is plaintext in the YAML and may be exposed through backups, source control, or file permissions. Environment variables can also be visible to sufficiently privileged local processes. Interactive passwords are read without terminal echo and retained only in process memory for the current command. Dockship's masking prevents terminal disclosure; it is not a secrets manager.
Keys containing tokens such as TOKEN, SECRET, PASSWORD, PASS, KEY, CREDENTIAL, or AUTH are masked in verbose build command output. This masking protects terminal output only. Docker build arguments can remain visible in image history; use BuildKit secrets instead for actual secrets.
Dockship image mappings are streamed over SSH stdin and are not printed in normal logs or stored locally. Application env files are never read or uploaded by Dockship.
--verboseprints resolved configuration paths, deployment-ID source, commands, and health retry details.--quietprints errors and the final essential result.--no-colororNO_COLOR=1disables ANSI color.--quietand--verboseare mutually exclusive.
dockship status production
dockship status production --jsonJSON mode passes through docker compose ps --format json output without transformation.
- “configuration file does not exist”: run
dockship initor pass the correct--configpath. - “required executable … was not found”: install it and ensure it is in
PATH. - Build failures stream Docker output and identify the image. Other concurrent builds are canceled.
- Transfer errors identify Docker save, local zstd, or the SSH/remote-load stage and retain bounded stderr.
- SSH behavior comes from your OpenSSH configuration; test
ssh <host>directly when authentication fails. - On older OpenSSH installations that do not support
SSH_ASKPASS_REQUIRE=force, use SSH keys/agent authentication. - Health checks run locally, so loopback URLs refer to the machine running Dockship, not the remote host.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General runtime failure |
| 2 | Invalid CLI usage |
| 3 | Invalid configuration |
| 4 | Missing dependency |
| 5 | Local Docker build or local-image failure |
| 6 | Image save or compression transfer failure |
| 7 | SSH/remote Docker load failure |
| 8 | Remote environment or Docker Compose failure |
| 9 | Health check failure |
Version one supports one host per target, zstd only, local builds, Linux/Unix remote hosts, and local HTTP health checks. It has no registry, rollback, deployment database, delta transfer, remote build, cache sync, image pruning, encrypted config, CI integration, orchestration platform integration, TUI, plugins, or self-update. A partial image deployment still runs the complete Compose project.
make fmt
make vet
make test
make test-race
make lint
make build
make install
make cleanDirect verification commands are:
gofmt -w .
go vet ./...
go test ./...
go test -race ./...
staticcheck ./...
go build -o bin/dockship .