Skip to content

fichte/gpd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gpd

Generic Deploy — a Bash orchestrator that turns a parent Docker-stack project's templates and per-environment variable files into a generated, validated, and deployed Docker Compose stack. It is intended to be embedded as a git submodule inside a parent project that owns the templates and secrets.

./gpd/gpd.sh -b /srv/docker -e prod -t full -g -p -d
#                 |          |       |     |  |  |
#                 |          |       |     |  |  └── deploy
#                 |          |       |     |  └───── push to target
#                 |          |       |     └──────── generate config
#                 |          |       └────────────── deploy type (see deployments file)
#                 |          └────────────────────── environment (see environments file)
#                 └───────────────────────────────── base directory on target

What it does

gpd.sh walks five workflow stages, gated by command-line flags:

Flag Stage Effect
-c clean Remove the previously generated docker/final/<env>/ tree
-g generate Render templates: copy → variable substitution (base/env/CI) → password hashing → optional GeoIP allow-list → .env
-p push Rsync the generated tree to the target host
-d deploy Pull images, dry-run, side-stack ordering, docker compose up -d
-u unused Prune dangling and old images on the target

local* environments skip the SSH/SCP/rsync transport and run docker compose directly on the host where gpd.sh runs. Any other environment ships over SSH to the host defined in its variables file.

Parent project layout (the contract)

Embed this repo at any path you like (the harness uses gpd/) and lay out the parent project so the script can find it via ${SCRIPT_DIR}/../docker/:

parent/
├── gpd/                                    # this repo, as a submodule
│   └── gpd.sh
└── docker/
    ├── variables/                          # how to assemble the stack
    │   ├── name                            # one line, the stack name (used in COMPOSE_PROJECT_NAME)
    │   ├── environments                    # one env per line: local, stage, prod, …
    │   ├── deployments                     # one deploy-type per line: full=svc1 svc2 …
    │   ├── mandatory                       # variable names required for every deploy
    │   └── acme                            # (optional) ACME issue-command driver
    ├── template/
    │   ├── compose/                        # service definitions, *.yml
    │   │   ├── nginx.yml                   # placeholders use __VAR__ tokens
    │   │   ├── nginx_extend.yml            # *_extend.yml overrides the base service
    │   │   └── …
    │   ├── config/                         # service config files (nginx.conf, …)
    │   ├── asset/                          # binary artifacts shipped with the stack
    │   └── variables/
    │       ├── nginx_template              # mandatory variable names *for this service*
    │       └── opensearch_template         # appended to the global mandatory list
    └── config/                             # per-environment, populated by CI
        ├── LOCAL_STACK_ENVIRONMENT_VARIABLES
        ├── LOCAL_STACK_SECRETS
        ├── LOCAL_STACK_DEPLOY_SSH_KEY
        ├── LOCAL_STACK_DEPLOY_SSH_KNOWN_HOSTS
        ├── LOCAL_STACK_CI_VARIABLES        # optional — falls back to CI env
        └── PROD_STACK_…                    # one set per environment

docker/final/<env>/ is generated by GPD at run time and is not committed.

File formats

  • variables/name — exactly one line, the stack name.

  • variables/environments — one environment per line. Names that start with local (e.g. local, local1, localdev) skip the SSH transport and run docker compose against the local Docker host.

  • variables/deployments — one deploy-type per line, format <deploy-type>=<service> <service> …. The <deploy-type> is what -t expects. Each service name corresponds to template/compose/<service>.yml, and optionally template/variables/<service>_template.

    full=acme nginx postgres opensearch portainer
    minimal=acme nginx
    
  • variables/mandatory — variable names (without env prefix) required for every deploy. Per-service files in template/variables/<service>_template are appended to this list whenever that service is part of the selected deploy-type.

    STACK_ADMIN_MANAGEMENT_PASSWORD
    STACK_DEPLOY_HOST
    STACK_DEPLOY_USER
    STACK_NETWORK_IPV4_SUBNET
    
  • config/<ENV>_STACK_ENVIRONMENT_VARIABLES and config/<ENV>_STACK_SECRETS — shell-source-able files. Variables are env-prefixed:

    LOCAL_STACK_ACME_DOMAIN=local.example.com
    LOCAL_STACK_ADMIN_MANAGEMENT_PASSWORD=…

    GPD reads <ENV>_STACK_X for every STACK_X listed in mandatory (or in a service template) and substitutes the value into every __STACK_X__ placeholder in template/compose/* and template/config/*.

  • config/<ENV>_STACK_DEPLOY_SSH_KEY / <ENV>_STACK_DEPLOY_SSH_KNOWN_HOSTS — required for non-local* environments. The script does not enforce file permissions; CI runners are expected to drop these with chmod 600.

  • variables/acme (optional) — variable names that contribute domain arguments to the ACME issue command. Only consulted when <ENV>_STACK_ACME_VAR1 matches ACME_EMPTY.

Placeholder substitution

Templates contain __TOKEN__ placeholders. Three substitution passes run in order:

  1. Base__STACK_ENV__, __STACK_ENV_LOWER__, __STACK_PATH__, __STACK_ASSET_PATH__, __STACK_COMPOSE_PATH__, __STACK_CONFIG_PATH__, __STACK_DATA_PATH__.
  2. Env — every name in the assembled mandatory list, sourced from the <ENV>_STACK_* files.
  3. CI__CI_REGISTRY__, __CI_REGISTRY_USER__, __CI_REGISTRY_PASSWORD__, __CI_PROJECT_PATH__, __CI_COMMIT_REF_NAME__, __CI_COMMIT_SHORT_SHA__.

Substitution treats values as opaque strings — passwords containing ;, &, \, $1, or $2y$10$… are inserted verbatim. If a value is destined for a docker-compose command:/environment: line, GPD doubles the literal $ characters ($$) so compose does not treat them as variable references.

Password generation

When template/compose/nginx.yml is part of the deploy:

Token Hash Used by
__STACK_ADMIN_PASSWORD__ bcrypt cost 8 (config files)
__STACK_ADMIN_10_PASSWORD__ bcrypt cost 10 (config files)
__PORTAINER_PASSWORD__ bcrypt cost 5 Portainer --admin-password
__WUD_PASSWORD__ apr1 / MD5 What's-up-Docker WUD_AUTH_BASIC_ADMIN_HASH
(file) nginx_nginxpasswd apr1 / MD5 nginx basic auth

When template/compose/opensearch.yml is part of the deploy, every __OPENSEARCH_<USER>_PASSWORD__ placeholder gets a bcrypt-12 hash sourced from <ENV>_STACK_OPENSEARCH_<USER>_PASSWORD.

Flag reference

Short Long Argument
-h --help print usage and exit
-b --basedir base directory on the target host (e.g. /srv/docker); no trailing slash
-e --environment environment name; must appear in variables/environments
-g --generate generate docker/final/<env>/
-p --push rsync docker/final/<env>/ to the target host
-d --deploy run docker compose up -d on the target
-t --deploy-type deploy type; must appear as a key in variables/deployments
-f --force bypass deploy safety checks (running services, dry-run)
-u --unused prune dangling/old images on the target
-c --clean remove the generated tree before re-generating
-o --geoip-disable skip the GeoIP database download and nginx allow-list
-r --retries=<N> attempts for flaky-network ops (rsync push, registry login, image pull, registry logout, GeoIP download); default 3, must be ≥ 1. Backoff between attempts is exponential (1s, 2s, 4s, …).

Combined invocations are common:

# generate locally for inspection
./gpd/gpd.sh -b /srv/docker -e local -t full -g

# CI: clean, generate, push, deploy
./gpd/gpd.sh -b /srv/docker -e prod -t full -c -g -p -d

# local stack on the dev machine (no SSH)
./gpd/gpd.sh -b ~/Docker/stack -e local1 -t full -c -g -d

Requirements

  • Bash 4 or newer. The script asserts this on startup; on macOS the default /bin/bash is 3.2 — install Homebrew bash with brew install bash.
  • GNU getopt. _usage.sh relies on --long-option syntax. macOS ships a BSD getopt that does not support it; install brew install gnu-getopt and prepend its bin/ to PATH (echo 'export PATH="$(brew --prefix gnu-getopt)/bin:$PATH"' >> ~/.zshrc).
  • Required at runtime: diff find grep perl rsync sha256sum sort ssh zstd, plus OpenSSL ≥ 1.1.1.
  • Preferred but optional:
    • htpasswd (apache-utils): used for password hashing when present; GPD falls back to python3 -m bcrypt for bcrypt and to openssl passwd -apr1 for apr1 hashes.
    • curl: used for downloads when present; GPD falls back to wget.
    • GNU readlink -e / realpath: used for symlink resolution; GPD falls back to BSD readlink -f and ultimately to perl Cwd::realpath.
  • Platforms tested: Linux, macOS (with brew install bash gnu-getopt), Windows Git-Bash and WSL.

License

GPL v3 — see LICENSE.

About

Generate Push Deploy is a small bash framework for creating and deploying docker-compose stacks

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages