On this page:
9.1 Pre and Post Scripts
9.2 Additional SSH Flags
9.3 Sudo
9.4 Example Nginx Config

9 Deployment🔗

The raco koyo deploy tool can be used in conjunction with raco koyo dist to perform blue-green deployments. For example:

  $ cd link-shortener

  $ npm run build

  $ raco koyo dist ++lang north

  $ raco koyo deploy dist/ v1.0 target-host-1 target-host-2 ...

The tool works by copying the given distribution to each of the target hosts, installing a systemd service, and tracking some state about which variant of the service is currently running. After uploading the distribution, it checks which variant is running, and starts the other variant, then runs health checks (if enabled) on the target variant. It writes an Nginx config file listing the host:port that the target variant is listening on and reloads Nginx. Finally, it stops the old variant and deletes any existing versions older than 30 days.

The target host must

For example, given an application called “link-shortener”, a call to raco koyo deploy dist/ v1.0 target-host will deploy the following files to target-host:

The application name is determined by looking at the name of the directory the tool is invoked in. The default deployment destination is a path constructed by prefixing the application name with "/opt/" (eg. "/opt/link-shortener/"). The application name can be customized using the --app-name flag and the destination using the --destination flag.

When health checking is enabled (the --health-check flag), the app’s executable is called with a -c PORT flag and deployment aborts if it exits with a non-zero exit code.

You can specify environment variables using the -e flag:

  $ raco koyo deploy \

      -e LINK_SHORTENER_DEBUG false \

      -e LINK_SHORTENER_URL_HOST link-shortener.example \

      dist v1.0 target-host

The environment variables are written to the "environment-blue" and "environment-green" files on deploy, depending on the variant being deployed, and loaded into the app’s runtime environment by the systemd service. The HOST, PORT, PLTUSERHOME, APP_NAME_HTTP_HOST and APP_NAME_HTTP_PORT environment variables are set automatically based on the target environment.

By default, the blue variant listens on localhost port 8001 and the green variant on 8002. These ports can be customized passing the --port flag. For example:

  $ raco koyo deploy \

      -p blue 8000 \

      -p green 9000 \

      dist v1.0 target-host

9.1 Pre and Post Scripts🔗

You can run arbitrary scripts before and after a service is deployed. The tool accepts the --pre-script flag and the --post-script flag for this purpose. The value of each of these flags must be the path to a script that will be copied to the target server and executed as a bash script with the old variant and the new variant as arguments.

The pre script executes after the service is installed, after the environment variable file is expanded, but before the service is started. The post script is executed after the service is successfully started, but before old deployments are deleted.

9.2 Additional SSH Flags🔗

The tool calls ssh and rsync to perform the deployment steps. Each call to these tools includes the flags -T -o StrictHostKeyChecking=accept-new. You can add flags to this list using the --ssh-flags flag.

One way to simplify SSH configuration is to store an infrastructure map in the form of an ssh_config file in the repo, then pass that file to the tool using the --ssh-flags flag. For example:

  $ raco koyo deploy \

      --ssh-flags '-F infra/ssh_config' \

      dist v1.0 prod-01 prod-02

Where "infra/ssh_config" might look something like this:

  Host bastion # jump host

    User root

    Port 22

    HostName X.XXX.XXX.XX

    IdentityFile ~/.ssh/deploy-key

    HostKeyAlias bastion

  

  Host prod-01 # app server 1

    User root

    Port 22

    HostName 10.0.0.1

    ProxyJump bastion

    IdentityFile ~/.ssh/deploy-key

    HostKeyAlias prod-01

  

  Host prod-02 # app server 2

    User root

    Port 22

    HostName 10.0.0.2

    ProxyJump bastion

    IdentityFile ~/.ssh/deploy-key

    HostKeyAlias prod-02

In this configuration, the publicly-accessible bastion is used as a jump box to connect to the prod-01 and prod-02 hosts through a private network and perform the deployment.

9.3 Sudo🔗

The tool uses sudo to perform the following commands:

When running as a non-root user, it needs to be able to perform these commands without being asked for a password. You can add the following configuration file to "/etc/sudoers.d/" to allow the SSH user to run these commands without being prompted for a password:

Don’t forget to replace the values in angle brackets with the appropriate values for your app.

  <user> \

    ALL=(ALL) \

    NOPASSWD: \

      /bin/chown -R * <destination>, \

      /bin/systemctl daemon-reload, \

      /bin/systemctl reload nginx, \

      /bin/systemctl start <app-name>@*.service, \

      /bin/systemctl stop <app-name>@*.service, \

      /usr/bin/tee /etc/systemd/system/<app-name>@.service

9.4 Example Nginx Config🔗

Here is an example Nginx virtual host config that points at a deployed "backends.conf" and proxies incoming requests to the deployed app. Static files are served directly from "/opt/link-shortener/versions/current/static/".

upstream link_shortener_backend {

  include /opt/link-shortener/backends.conf;

 

  keepalive 128;

}

 

server {

  listen 80;

  server_name link-shortener.example;

 

  gzip            on;

  gzip_vary       on;

  gzip_proxied    any;

  gzip_comp_level 6;

  gzip_types      text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;

 

  location /static/ {

    root /opt/link-shortener/versions/current;

 

    sendfile          on;

    tcp_nopush        on;

    tcp_nodelay       on;

    keepalive_timeout 65;

 

    expires    max;

    add_header Cache-Control public;

  }

 

  location / {

    client_max_body_size 20M;

 

    add_header "Referrer-Policy" "no-referrer" always;

    add_header "X-Content-Type-Options" "nosniff" always;

    add_header "X-Frame-Options" "DENY" always;

    add_header "X-XSS-Protection" "1; mode=block" always;

 

    proxy_pass             http://link_shortener_backend;

    proxy_http_version     1.1;

    proxy_connect_timeout  60s;

    proxy_send_timeout     60s;

    proxy_read_timeout     60s;

    proxy_set_header       Connection      "";

    proxy_set_header       Host            $host;

    proxy_set_header       X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_set_header       X-Real-IP       $remote_addr;

    proxy_intercept_errors on;

  }

}