Quick Start

Once installed, Fapistrano bootstraps a fap command to complete release/rollback/restart task.

First Run

Add a file named deploy.yml:

project_name: smallest-example
app_name: smallest-example
user: deploy
use_ssh_config: true

stage_role_configs:
  staging:
    app:
      hosts:
        - app-stag01

After Runing command below, Fapistrano will create a smallest example on your host:

$ fap release --stage staging --role app
[app-stag01] Executing task 'release'
===> Starting
[app-stag01] run: mkdir -p /home/deploy/www/smallest-example/{releases,shared/log}
[app-stag01] run: chmod -R g+w /home/deploy/www/smallest-example/shared
[app-stag01] run: mkdir -p /home/deploy/www/smallest-example/releases/160328-162832
===> Started
===> Updating
===> Updated
===> Publishing
[app-stag01] run: ln -nfs /home/deploy/www/smallest-example/releases/160328-162832 /home/deploy/www/smallest-example/current
===> Published
===> Finishing
[app-stag01] run: ls -x /home/deploy/www/smallest-example/releases
===> Finished

For now, it does nothing but leaving a blank directory hierachy to you:

$ ssh deploy@app-stag01 tree /home/deploy/www/smallest-example/
/home/deploy/www/smallest-example/
├── current -> /home/deploy/www/smallest-example/releases/160328-162832
├── releases
│   └── 160328-162832
└── shared
    └── log

5 directories, 0 files

Using Git

Git is a popular scm tool. Fapistrano integrated git as a plugin. You need to declare loading fapistrano.git and specify repository in deploy.yml:

project_name: git-example
app_name: git-example
user: deploy
use_ssh_config: true

stage_role_configs:
  staging:
    app:
      hosts:
        - app-stag01

plugins:
  - fapistrano.plugins.git

repo: git@github.com:octocat/Hello-World.git
branch: master

By Executing release flow, Fapistrano clones remote git repo and export code in master branch to release directory:

$ fap release --stage staging --role app
[app-stag01] Executing task 'release'
===> Starting
[app-stag01] run: mkdir -p /home/deploy/www/git-example/{releases,shared/log}
[app-stag01] run: chmod -R g+w /home/deploy/www/git-example/shared
[app-stag01] run: mkdir -p /home/deploy/www/git-example/releases/160328-163937
===> Started
[app-stag01] run: git clone --mirror --depth 1 --no-single-branch git@github.com:octocat/Hello-World.git /home/deploy/www/git-example/repo
[app-stag01] run: git ls-remote --heads git@github.com:octocat/Hello-World.git
===> Updating
[app-stag01] run: git fetch --depth 1 origin master
[app-stag01] run: git rev-list --max-count=1 --abbrev-commit --abbrev=12 master
[app-stag01] run: git archive master | tar -x -f - -C /home/deploy/www/git-example/releases/160328-163937/
[app-stag01] run: echo 7fd1a60b01f9 >> REVISION
===> Updated
===> Publishing
[app-stag01] run: ln -nfs /home/deploy/www/git-example/releases/160328-163937 /home/deploy/www/git-example/current
===> Published
===> Finishing
[app-stag01] run: ls -x /home/deploy/www/git-example/releases
===> Finished

Now we have added a repo directory and updated out release directory:

% ssh deploy@lws-stag01 tree /home/deploy/www/git-example
/home/deploy/www/git-example
├── current -> /home/deploy/www/git-example/releases/160328-163937
├── releases
│   └── 160328-163937
│       ├── README
│       └── REVISION
├── repo
│   ├── branches
│   ├── config
│   ├── description
│   ├── FETCH_HEAD
│   ├── HEAD
│   ├── hooks
│   │   ├── applypatch-msg.sample
│   │   ├── commit-msg.sample
│   │   ├── post-update.sample
│   │   ├── pre-applypatch.sample
│   │   ├── pre-commit.sample
│   │   ├── prepare-commit-msg.sample
│   │   ├── pre-push.sample
│   │   ├── pre-rebase.sample
│   │   └── update.sample
│   ├── info
│   │   └── exclude
│   ├── objects
│   │   ├── info
│   │   └── pack
│   │       ├── pack-c8b9cbbd14e791b8beddf1033f5b4357d4f179da.idx
│   │       └── pack-c8b9cbbd14e791b8beddf1033f5b4357d4f179da.pack
│   ├── packed-refs
│   ├── refs
│   │   ├── heads
│   │   └── tags
│   └── shallow
└── shared
    └── log

Using Supervisor

A static git repository updating is trivial. Let’s get a little bit more complicated now.

We are going to define deploy.yml for a flask hello-world application:

project_name: supervisor-example
app_name: supervisor-example
user: deploy
use_ssh_config: true

stage_role_configs:
  staging:
    app:
      hosts:
        - app-stag01

plugins:
  - fapistrano.plugins.git
  - fapistrano.plugins.virtualenv
  - fapistrano.plugins.supervisorctl

repo: git@github.com:liwushuo/fapistrano.git
git_archive_tree: examples/supervisor-example

supervisor_check_status: true
supervisor_conf: configs/supervisor_%(stage)s_%(role)s.conf

Then we add configs/supervisor_staging_app.conf to the repository:

[program:supervisor-example]
command=python app.py
directory=/home/deploy/www/%(program_name)s/current
environment=PATH="/home/deploy/www/%(program_name)s/current/venv/bin",FLASK_ENV="stag"
numprocs=1
user=deploy
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s-web.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10

Finally, Run with option –supervisor-refresh=true, since we first registered our supervisor config to supervisord. In the next release, there is no need to add option –supervisor-refresh=true unless supervisor config file modified.:

[app-stag01] Executing task 'release'
===> Starting
[app-stag01] run: mkdir -p /home/deploy/www/supervisor-example/{releases,shared/log}
[app-stag01] run: chmod -R g+w /home/deploy/www/supervisor-example/shared
[app-stag01] run: mkdir -p /home/deploy/www/supervisor-example/releases/160328-173812
===> Started
[app-stag01] run: git clone --mirror --depth 1 --no-single-branch git@github.com:liwushuo/fapistrano.git /home/deploy/www/supervisor-example/repo
[app-stag01] run: git ls-remote --heads git@github.com:liwushuo/fapistrano.git
[app-stag01] run: ln -nfs /home/deploy/www/supervisor-example/current/configs/supervisor_staging_app.conf /etc/supervisor/conf.d/supervisor-example.conf
===> Updating
[app-stag01] run: git fetch --depth 1 origin master
[app-stag01] run: git rev-list --max-count=1 --abbrev-commit --abbrev=12 master
[app-stag01] run: git archive master examples/supervisor-example | tar -x --strip-components 2 -f - -C /home/deploy/www/supervisor-example/releases/160328-173812/
[app-stag01] run: echo 86ba572f3d8e >> REVISION
===> Updated
[app-stag01] run: /usr/bin/env virtualenv /home/deploy/www/supervisor-example/releases/160328-173812/venv
[app-stag01] run: pip install -U pip setuptools wheel
[app-stag01] run: pip install -r /home/deploy/www/supervisor-example/releases/160328-173812/requirements.txt
===> Publishing
[app-stag01] run: ln -nfs /home/deploy/www/supervisor-example/releases/160328-173812 /home/deploy/www/supervisor-example/current
===> Published
[app-stag01] run: supervisorctl stop supervisor-example
[app-stag01] run: supervisorctl reread
[app-stag01] run: supervisorctl update
[app-stag01] run: supervisorctl start supervisor-example
[app-stag01] run: supervisorctl status supervisor-example
[app-stag01] out: supervisor-example               RUNNING    pid 13014, uptime 0:00:02
[app-stag01] out:

===> Finishing
[app-stag01] run: ls -x /home/deploy/www/supervisor-example/releases
[app-stag01] run: rm -rf 160328-173248
===> Finished

Now, our flask application is running!:

$ ssh deploy@app-stag01 curl -s http://0.0.0.0:5000
hello world

Rollback!

Rollback is easily by replacing release to rollback.

After releasing again, our release are now at 160328-175016.

let’s trigger a rollback flow.:

$ fap rollback --stage staging --role app
staging app
[app-stag01] Executing task 'rollback'
===> Starting
[app-stag01] run: readlink /home/deploy/www/supervisor-example/current
[app-stag01] run: ls -x /home/deploy/www/supervisor-example/releases
===> Started
[app-stag01] run: git ls-remote --heads git@github.com:liwushuo/fapistrano.git
[app-stag01] run: ln -nfs /home/deploy/www/supervisor-example/current/configs/supervisor_staging_app.conf /etc/supervisor/conf.d/supervisor-example.conf
===> Reverting
===> Reverted
===> Publishing
[app-stag01] run: ln -nfs /home/deploy/www/supervisor-example/releases/160328-173812 /home/deploy/www/supervisor-example/current
===> Published
[app-stag01] run: supervisorctl restart supervisor-example
[app-stag01] run: supervisorctl status supervisor-example
[app-stag01] out: supervisor-example               RUNNING    pid 15272, uptime 0:00:02
[app-stag01] out:

===> Finishing rollback
[app-stag01] run: rm -rf /home/deploy/www/supervisor-example/releases/160328-175016
===> Finished

This command help us rollback our current release back to 160328-173812, which is deployed in last example.

Using with Beeper

Beeper is a tool bundling virtualenv.py, wheels and our project as a tar file. With the help of Jenkins, we can build a beeper tgz file before release.

We can use a curl plugin to update our application:

project_name: curl-example
app_name: curl-example
user: deploy
use_ssh_config: true
stage_role_configs:
  staging:
    app:
      hosts:
        - app-stag01
plugins:
  - fapistrano.plugins.curl
  - fapistrano.plugins.supervisorctl

curl_extract_tar: true
curl_postinstall_script: "sh ./install.sh"

supervisor_check_status: true

When running fap release, we can attach option –curl-url. Assuming you are using Jenkins to build your application and have authority to fetch artifact:

$ fap release -s staging -r app --curl-url=http://ci.your-corp.com/view/Server/job/server.builder.curl-example/lastSuccessfulBuild/artifact/dist/curl-example-0f2da63.tar --curl-options="--user $JENKINS_USERNAME:$JENKINS_TOKEN"

Using with Jar

If there is a Java application to deploy, it’s recommend to build it to a Jar file. You can integrate fapistrano.plugins.curl to deploy jar.:

project_name: jar-example
app_name: jar-example
user: deploy
use_ssh_config: true
stage_role_configs:
  staging:
    app:
      hosts:
        - app-stag01
plugins:
  - fapistrano.plugins.localshared
  - fapistrano.plugins.curl
  - fapistrano.plugins.supervisorctl

# supervisor conf
supervisor_refresh: false
supervisor_output: false
supervisor_check_status: true

# curl conf
curl_output: 'bayarea.jar'