Create a simple app

First lets create a new, simple sintara app

1
2
3
  bundle init
  bundle add sinatra puma
  bundle add rerun --group development

A simple config.ru:

1
2
3
  require File.expand_path('app', File.dirname(__FILE__))

  run App

And then a very simple app.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  require 'sinatra/base'

  class App < Sinatra::Base
    get '/' do
      "Hello world!!!"
    end

    get '/up' do
      "Yup"
    end
  end

Test with

1
  bundle exec rackup

Install mrsk

1
2
  bundle add mrsk --group development
  alias mrsk="bundle exec mrsk"

Create the config file

1
  mrsk init

Created configuration file in config/deploy.yml Created .env file Created sample hooks in .mrsk/hooks

This will create config/deploy.yml. For these tests, I created a server and it's IP is below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
service: sampleapp
image: wschenk/sampleapp

servers:
  - 95.216.175.53

registry:
  username: wschenk
  password:
    - MRSK_REGISTRY_PASSWORD

Create docker token

I'm using dockerhub to host my images. You'll need to generate a token, and then put it in your .env file.

Best practices are to not store the token in the file, but pull them out using a password manager. I'm using lasspass, so it looks like this:

MRSK_REGISTRY_PASSWORD=<%= `lpass show "Docker Token" | awk '/Notes/ {print $2}'`.strip %>

Which we can then generate .env from using

1
mrsk envify

Be sure to put .env into .gitignore!

Create docker file

Make sure that you have curl installed in your image, otherwise the health checks will fail!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  ARG RUBY_VERSION=3.1.4
  FROM ruby:$RUBY_VERSION-slim as base

  RUN apt-get update -qq && \
      apt-get install --no-install-recommends -y build-essential curl

  RUN gem update --system --no-document && \
      gem install -N bundler:2.2.32 && \
      bundle config set --local without development

  # Rack app lives here
  WORKDIR /app

  # Install application gems
  COPY Gemfile* .
  RUN bundle install --without development

  RUN useradd ruby --home /app --shell /bin/bash
  USER ruby:ruby

  # Copy application code
  COPY --chown=ruby:ruby . .

  # Start the server
  EXPOSE 3000
  CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "--port", "3000"]

Setup the servers

The initial run for this took me 230 seconds.

1
  mrsk setup

One problem I had initially was that I didn't have curl in my image, which didn't make it work very well!

Testing

On your local machine, run curl

1
curl http://95.216.175.53

Hello world!

Now if we change our app.rb and run mrsk deploy, it only takes 29 seconds and we can see

1
curl http://95.216.175.53

Hello world! From the redeploy

Setting up a remote builder

I'm building this locally on my M1 mac, and docker is running through emulation. Lets have it build everything on the remote machine. Add this to the config/deploy.yml

1
2
3
4
  builder:
    remote:
      arch: amd64
      host: ssh://root@95.216.175.53

This only builds the amd64 architecture, and it happens on a remote server. Which happens to be the server that we are deploying to.

Setting up an appliance

a/k/a redis

Lets see how to add a redis database to our system.

First we need to add the accessory to our config/deploy.yml file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  accessories:
    redis:
      image: redis:latest
      roles:
        - web
      port: "36379:6379"
      volumes:
        - /var/lib/redis:/data
  env:
    REDIS_URL: redis://95.216.175.53:36379/1

Then we need to set it up:

1
  mrsk accessory boot redis

We can then see the status

1
  mrsk accessory details redis

INFO [4e06f84c] Running docker ps –filter label=service=sampleapp-redis on 95.216.175.53 INFO [4e06f84c] Finished in 1.446 seconds with exit status 0 (successful). CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 712f345b9cbb redis:latest "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 0.0.0.0:36379->6379/tcp, :::36379->6379/tcp sampleapp-redis

This starts up the app. Now we need to update our code to actually use it!

1
  bundle add redis

Then our app.rb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  require 'sinatra/base'
  require 'redis'

  class App < Sinatra::Base
    get '/' do
      redis = Redis.new( url: ENV['REDIS_URL'] )
      count = redis.incr("counter")
      "Hello world, called #{count} times"
    end

    get '/up' do
      "Yup"
    end
  end
1
curl http://95.216.175.53

Hello world, called 1 times

And

1
curl http://95.216.175.53

Hello world, called 2 times

Thoughts

I like tools that let you throw together sites really quickly. This is really interesting since it makes it easy to setup a fleet of servers, though that isn't something that I need to do that often.

You'd need to setup load balancers in front and probably something like cloudflare to handle SSL and all that stuff, but its way simplier than kubernetes.

I really like the ability to have it run off of a totally unsetup server, it installs docker and everything else for you. Very nifty.

I'd like to see something like this but configured Caddy instead and let you host many sites on one platform.

Previously

Introduction to Filespooler

2023-06-03

Next

An Interview with Nicolas Rougier, Creator of NANO Emacs

2023-06-05

labnotes

Previously

Using lastpass from CLI or script better than keeping files around

2023-04-06

Next

Controlling Hetzner with CLI Simple wrapper scripts

2023-06-19