Skip to content
Switch branches/tags
Go to file

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time
Jun 9, 2017
Jun 9, 2017
Jun 11, 2017

πŸ‘¨β€πŸ­ Welder

Welder allows you to set up a Linux server with plain shell scripts.

I wrote it out of frustration with Ansible. Ansible is an amazing and powerful tool, but for my needs it's just too much. 90% of the time all I need is:

ssh -t "$(< ./"
# or:
ssh -t "$(cat ./"

In most basic terms, that's what welder does.

But there's some more.


Welder allows you to:

  • execute local shell scripts on the server via ssh
  • organize your scripts into a logical set of reusable modules
  • set up a server with a single command (welder run <playbook>)
  • run one-off shell scripts (welder run-script <> <path/to/>)
  • use liquid templates for configuration (optional)
  • enter sudo password just once per playbook

See welder-contrib for some example modules.

Directory structure

An example directory structure:

β”œβ”€β”€ modules
β”‚Β Β  β”œβ”€β”€ nginx
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ files
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ nginx.conf
β”‚Β Β  β”‚Β Β  └──
β”‚Β Β  β”œβ”€β”€ rails
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ files
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ nginx
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ site.conf.liquid
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ systemd
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚   β”œβ”€β”€ puma.service.liquid
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”‚   └── sidekiq.service.liquid
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── rbenv-vars.liquid
β”‚Β Β  β”‚Β Β  └──
β”‚Β Β  β”œβ”€β”€ system
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ files
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ 10periodic
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── 50unattended-upgrades
β”‚Β Β  β”‚Β Β  └──
β”œβ”€β”€ config.yml
β”œβ”€β”€ vault.yml
β”œβ”€β”€ vault.yml.gpg
└── my-site.yml

Example playbook:

ssh_port: 22  # Optional (default: 22)

shared_path: ../shared # optional

# List of modules to execute
  - system
  - firewall
  - rbenv
  - nginx
  - rails


Welder uses liquid for templates. It's mostly compatible with ansible's *.j2 files:

# modules/rails/files/nginx-site.conf.liquid
upstream thumbor {
  {% for port in thumbor_instances %}
      server{{ port }};
  {% endfor %}

server {
    listen 80;

    server_name {{ thumbor_host }};
    include snippets/ssl-{{ app_domain }}.conf;

    location / {
        proxy_pass http://thumbor;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;


The config.yml file will be used to provide variables for your *.liquid templates:

# example config.yml
app_name: example

ruby_version: "2.4.0"
ruby_deploy_user: "deploy"
rails_env: production

app_dir: "/var/www/example"
letsencrypt_web_dir: "/var/www/letsencrypt"

  - 8000
  - 8001
  - 8002
  - 8003

During the compilation phase, config.yml is turned into a bash-compatible format and uploaded to the server:

# compiled config-variables file
cfg_thumbor_instances=(8000 8001 8002 8003)

You can then source it in your setup scripts:

# modules/example/
source setup/config-variables

echo $cfg_app_name
echo $cfg_app_dir
# (notice the $cfg_ prefix)

NOTE: In order for this to work reliably, config.yml has to be fairly flat and simple - nested hashes are not supported.

Shared Modules

If you want to avoid duplicating modules across different projects, you can specify shared_path in your playbook YML file:


shared_path: ../shared

  - system
  - firewall

In the example above, ../shared directory should contain modules directory.

Security Notes

Don't store any sensitive information (passwords etc.) in config.yml. If you want to keep passwords in git, create a vault.yml file, add it to .gitignore and store the encrypted version in revision control:

# encrypt & decrypt vault.yml using your gpg key
gpg --encrypt --recipient 'John Doe' vault.yml
gpg --decrypt --output vault.yml vault.yml.gpg

# encrypt & decrypt using a passphrase (no private/public key needed)
gpg --symmetric --cipher-algo aes256 vault.yml
gpg --decrypt --output vault.yml --cipher-algo aes256 vault.yml.gpg

For more information on how to set up GPG/PGP, see this excellent tutorial.

Think of this as just another level of security for your private git repos. You probably don't want to store the encrypted vault in a public repo.


Because sudo password is passed as an argument to the expect script, it will be visible in the process list on your local computer. This could be an issue if you're using a shared machine to run setup scripts.

Example setup script

# modules/nginx/

set -xeu # 'u' will give you warnings on unbound config variables

[[ -f setup/config-variables ]] && source setup/config-variables

sudo add-apt-repository -y ppa:nginx/stable
sudo apt-get update && sudo apt-get install -y nginx

sudo service nginx start

sudo cp setup/modules/nginx/files/nginx.conf /etc/nginx/nginx.conf

# Disable default site
if [ -f /etc/nginx/sites-enabled/default ]; then
  sudo rm /etc/nginx/sites-enabled/default

sudo service nginx restart


welder run my-site # runs the playbook defined in my-site.yaml

The run script will compile templates and configs, upload them to the server (to /home/username/setup) and then it will ask you for the sudo password. After that, it will execute all *.sh scripts from the modules listed in the playbook file.

Additional commands:

welder compile <playbook>  # compiles templates and uploads them to the server
welder cleanup <playbook>  # remove compiled files from the server

If you want to run a single *.sh script on the server, you can use this:

welder run-script <> <path/to/>

NOTE: the run-script command does not compile templates. It merely wraps ssh -t "$(< ./path/to/". If you want access to templates and config, run welder compile <playbook> first and welder cleanup <playbook> when you're done.


  1. Install dependencies

    Welder requires expect, rsync and ruby. Ruby is used mainly as a convenient way to parse YAML configuration files.

    Optionally, if you'd like to use the templating feature, you need to install liquid gem:

    $ gem install liquid
  2. Check out welder into ~/Code/welder (or whatever location you prefer):

    $ git clone ~/Code/welder
  3. Add ~/Code/welder/bin to your $PATH for access to the welder command-line utility.

    $ echo 'export PATH="$PATH:$HOME/Code/welder/bin"' >> ~/.bash_profile

    Ubuntu Desktop note: Modify your ~/.bashrc instead of ~/.bash_profile.

    Zsh note: Modify your ~/.zshrc file instead of ~/.bash_profile.

  4. Restart your shell so that PATH changes take effect. (Opening a new terminal tab will usually do it.) Now check if welder was set up:

    $  which welder


Since welder allows you to run anything on the server, you should use it with caution. It won't protect you from screw-ups, like rm -rf "/$undefined_variable".

Use at your own risk.


There's an alternative version of welder, re-implemented in Python by @thomas-mc-work.


πŸ‘¨β€πŸ­Set up your Linux server with plain shell scripts





No releases published


No packages published