Sign inSign up

blackhost/deployer

By blackhost

•Updated about 20 hours ago

Deploy code via FTP/FTPS/SFTP (lftp), or SSH (rsync) and run pre/post commands on remote servers.

Image
Integration & delivery
Developer tools
1

365

blackhost/deployer repository overview

⁠🚀 DEP 9000

Deployer 9000 is a small Alpine-based image that syncs a directory to a remote server over FTP / FTPS, SFTP or rsync over SSH, with optional commands before and after the transfer. It was built for GitHub Actions⁠ and runs anywhere where a container can reach your server: GitLab CI, a cron job, your laptop.

Unlike its infamous cousin HAL, this unit has one mission: get your code deployed, no backtalk.

  • Protocols: FTP, FTPS with TLS verification, SFTP, rsync over SSH
  • Configured by environment variables, nothing else to mount or install
  • Pre/post deploy commands on the remote host over SSH
  • Delta uploads: with PRESERVE_TIMES only the files that changed are uploaded, nothing is stored on the server
  • Safe by default: dotfiles stay off the server, a deploy that would wipe the target is refused, SSH host keys can be pinned
  • Dry run that logs in and shows what would change
  • Minimal: Alpine, lftp, rsync, OpenSSH client, git, bash

⁠Quick start: GitLab CI

deploy:
  image: blackhost/deployer:1
  stage: deploy
  script: [deployer]
  variables:
    PROTOCOL: sftp
    SERVER: example.com
    REMOTE_DIR: public_html
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Add USERNAME and PASSWORD (or SSH_KEY) as masked CI/CD variables in Settings → CI/CD → Variables. The job runs inside your checked-out repository, so LOCAL_DIR defaults to the project root.

Two jobs for two targets is just two variables: blocks:

deploy-api:
  extends: deploy
  variables: { SERVER: api.example.com, PASSWORD: *** }
  rules: [{ if: $CI_COMMIT_BRANCH == "api" }]

Keep deploy settings inside the job. Project-wide variables with the same names (PORT, DRY_RUN, DELETE, PARALLEL) are picked up too.

On GitHub? Deployer 9000 is also a GitHub Action⁠, same options written as with: inputs. Setup guide: GitHub Actions: Setup & Usage⁠.

⁠Quick start: docker run

docker run --rm -v "$PWD:/deploy" \
  -e PROTOCOL=sftp -e SERVER=example.com -e REMOTE_DIR=public_html \
  -e USERNAME=deploy -e PASSWORD="***" \
  blackhost/deployer:1

The image's working directory is /deploy. Mount the folder you want to ship there, or set LOCAL_DIR. Add -e DRY_RUN=true to see the plan first.

⁠Quick start: GitHub Actions

Use the action instead of the image directly, inputs map one to one to the variables below:

- uses: Black-HOST/deployer@v1
  with:
    server: ${{ secrets.FTP_HOST }}
    username: ${{ secrets.FTP_USER }}
    password: *** secrets.FTP_PASS }}
    remote_dir: public_html

Full documentation at https://github.com/Black-HOST/deployer⁠.

⁠Configuration

Every option is an environment variable. GitHub Actions users write the same names in lowercase under with:.

VariableDefaultDescription
PROTOCOLftpftp, sftp or rsync
SERVERrequiredHostname or IP of the target server
PORT21 for ftp, 22 for sftp and rsyncRemote port
USERNAMErequiredLogin user
PASSWORDPassword for FTP, SFTP, rsync, or the passphrase of SSH_KEY
SSH_KEYPrivate key for SFTP and rsync, the key contents in PEM format
HOST_KEYPublic SSH host key of the server, for example ssh-ed25519 AAAA..., one per line. When set, any other host key is refused. SFTP and rsync only
LOCAL_DIR.Directory to upload, relative to the working directory
REMOTE_DIR/Target directory on the server, relative paths resolve from the login directory
DELETEfalseRemove remote files that no longer exist locally. Read the warning below
ONLY_NEWERfalseSkip files whose remote copy has a newer timestamp
DRY_RUNfalseLog in and print what would be transferred, change nothing
PRESERVE_TIMESfalseGive every file tracked by git the time of its last commit before the transfer. FTP and SFTP then upload only the files that changed
SAFEGUARDStrueRefuse to deploy when DELETE is enabled and there is nothing to deploy, which would wipe REMOTE_DIR
EXCLUDE.*,.*/,node_modules/,*.logComma or newline separated glob patterns. The default skips dotfiles and dot-directories, so .git and .env never leave the runner. A directory needs a trailing slash
INCLUDEComma or newline separated glob patterns that are deployed even when excluded, for example .htaccess,.well-known/
PRE_SCRIPTCommand(s) run on the server over SSH before the transfer. SFTP and rsync only
POST_SCRIPTCommand(s) run on the server over SSH after the transfer. SFTP and rsync only
REMOTE_SHELL/bin/bash -lcShell used to run PRE_SCRIPT and POST_SCRIPT
SECUREtrueFTP only: use FTPS (TLS)
VERIFY_TLStrueFTP only: verify the server certificate
PASSIVEtrueFTP only: passive mode
PARALLEL2FTP and SFTP only: parallel transfers, 1 to 5 is safe
EXTRA_LFTPFTP and SFTP only: raw lftp commands injected before the transfer. Advanced, and a failing command fails the job

Boolean values accept true, false, yes, no, 1, 0, on, off.

⁠Behaviour worth knowing

  • A deploy makes the server match the source. A CI job starts from a fresh checkout, so without PRESERVE_TIMES every file looks new and FTP and SFTP upload all of them. Set PRESERVE_TIMES=true in a git checkout to upload only what changed; shallow checkouts are completed automatically.
  • Uploads replace files atomically. Over FTP and SFTP a file is uploaded under a temporary name and renamed when complete, so a changed file is never missing during a deploy.
  • DELETE=true removes files. Anything under REMOTE_DIR that is not in LOCAL_DIR and not excluded is deleted, and that covers dotfiles you list in INCLUDE. Run with DRY_RUN=true first, every time you change the exclude list. With nothing to deploy the job is refused, unless SAFEGUARDS=false.
  • Dry runs fail like real runs. Wrong host, port, TLS or credentials exit non-zero, so a dry-run job on merge requests is a real check.
  • Host keys are accepted on first connection, trust on first use. Set HOST_KEY to trust one key only and refuse everything else.
  • Exit code is non-zero whenever a transfer, a pre or post script, or a login fails. Your pipeline sees it.

⁠Tags

TagMeaning
1.3.0exact release
1.3latest 1.3.x
1latest 1.x, recommended for pipelines
latestnewest release

Images are built from tagged releases of the GitHub repository and are also published on ghcr.io/black-host/deployer.

Tag summary

Content type

Image

Digest

sha256:c96c9f942…

Size

14.4 MB

Last updated

about 20 hours ago

docker pull blackhost/deployer