Skip to content

Repository files navigation

Dockship

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.

Architecture

  • The root package is the public install entrypoint; cmd/dockship remains as a compatibility entrypoint. Both delegate signal handling and process exit codes to internal/entrypoint.
  • internal/cli defines Cobra commands and user-facing workflows.
  • internal/config strictly decodes YAML, applies explicit defaults, resolves local paths relative to the config file, and aggregates validation errors.
  • internal/deployment creates side-effect-free plans and orchestrates builds, streaming transfer, Compose, environment files, and health checks.
  • internal/process centralizes local command execution and bounded stderr capture.
  • internal/webpanel serves the local control deck, serializes command execution, streams logs with server-sent events, and keeps non-secret event summaries in the user cache.
  • ui and web contain 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/shell is the only place that quotes dynamic POSIX shell values.
  • internal/sshaskpass provides Dockship's dependency-free OpenSSH askpass helper mode.
  • internal/apperror maps 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.

Requirements

  • 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.

Installation

go install github.com/vahidid/dockship@latest

Or build a repository-local binary:

go build -o bin/dockship .

Quick start

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 web

The 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.

Web control panel

dockship web

The 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:8088

The panel can:

  • initialize and validate a configuration without running dockship init;
  • queue and cancel deploy, build, status, and config validate operations;
  • 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:web

Interactive 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.

Configuration

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: 5

build.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.

SSH authentication

Authentication uses the first available non-empty source in this order:

  1. The environment variable named by the target's ssh_password_env.
  2. The global DOCKSHIP_SSH_PASSWORD environment variable.
  3. The target's plaintext ssh_password value.
  4. An OpenSSH key or SSH agent, checked with BatchMode=yes.
  5. If key/agent authentication is rejected, a no-echo password prompt on the local terminal.

Examples:

export DOCKSHIP_SSH_PASSWORD='your-password'
dockship deploy production

Or use a target-specific variable without putting the password in YAML:

targets:
  production:
    host: deploy@example.com
    ssh_password_env: PRODUCTION_SSH_PASSWORD

Configured 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.

Multi-image deployments

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 backend

Only 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.

Remote Docker Compose setup

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 -d

Later 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.

Image tags

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-21

With --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.

Dry run

dockship --verbose deploy production --dry-run

Dry-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.

Health checks

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.

Security considerations

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.

Output controls

  • --verbose prints resolved configuration paths, deployment-ID source, commands, and health retry details.
  • --quiet prints errors and the final essential result.
  • --no-color or NO_COLOR=1 disables ANSI color.
  • --quiet and --verbose are mutually exclusive.

Status

dockship status production
dockship status production --json

JSON mode passes through docker compose ps --format json output without transformation.

Troubleshooting

  • “configuration file does not exist”: run dockship init or pass the correct --config path.
  • “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.

Exit codes

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

Current limitations

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.

Development

make fmt
make vet
make test
make test-race
make lint
make build
make install
make clean

Direct verification commands are:

gofmt -w .
go vet ./...
go test ./...
go test -race ./...
staticcheck ./...
go build -o bin/dockship .

About

Simple CLI application for shipment docker images on servers without extra tools and registry.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages