Posted on • Edited on • Originally published atodone.me
Rewriting to Haskell–Deployment
You can keep reading here orjump to my blog to get the full experience, including the wonderful pink, blue and white palette.
This is part of a series:
Deploy with Hapistrano
Stream was born as a Rails application. For that reason, we have been usingCapistrano to deploy it. That's why for the Servant code we have decided to employHapistrano:
Hapistrano makes it easy to reliably deploy Haskell applications to a server.
Following popular libraries like Ruby's , Hapistrano does the work of building the application with dependencies into a distinct folder, and then atomically moves a symlink to the latest complete build.
This allows for atomic switchovers to new application code after the build is complete. Rollback is even simpler, since Hapistrano can just point the current symlink to the previous release.
This is how we are currently using Hapistrano to deploy the code:
hap deploy# orHAPISTRANO_REVISION=origin/feature_branch hap deploy
What follows is ourhap.yaml
:
deploy_path:'/home/stream/application-hs'host:stream@stream.example.comssh_args:-"-A"# SSH agent forwardingrepo:'git@github.com:LunarLogic/stream.git'revision:"_env:HAPISTRANO_REVISION:origin/master"build_script:-cd haskell && stack setup-cd haskell && stack build-cd haskell && stack install --local-bin-path .restart_command:sudo systemctl restart stream-hs
Server
First of all, we need to have Stack installed. This is needed because, with the above configuration, Hapistrano will build the app on the server on each deploy.
sudowget-qO- https://get.haskellstack.org | sh
Secondly, we decided that, for the time being, we will be serving the Servant code under/servant
. Also, our Servant app will be running on port 8080. So let's have Nginx do the right thing:
location /servant{ proxy_pass http://127.0.0.1:8080;}
Thirdly, we want Systemd to manage the Servant process. What follows is the unit configuration we are using:
[Unit]Description=Servant AppAfter=nginx.serviceAfter=syslog.targetAfter=network.target[Service]Type=simpleRestart=alwaysExecStart=/home/stream/application-hs/current/haskell/haskell-exe# ^ `deploy_path` for Hapistrano.# ^ `current` is where Hapistrano keeps the latest deployed app.# ^ We keep the Servant code in the repo in the `haskell/` folder.# ^ Name of the executable.WorkingDirectory=/home/stream/application-hs/current/haskellStandardOutput=syslogStandardError=syslogSyslogIdentifier=servantUser=stream[Install]WantedBy=multi-user.target
Notice thathaskell-exe
lives inside thecurrent
release (i.e. latest) because we configure Hapistrano tostack install --local-bin-path .
.
Lastly, we need to allow the stream user to restart the application by adding them to/etc/sudoers
:
streamALL=(ALL) NOPASSWD: /bin/systemctl restart stream-hs
We automated all of the steps with Ansible.
We invoke it with:
- role: haskell haskell__app_name: stream haskell__username: stream
And here's the role:
- name: Install Stack shell:"sudo wget -qO- https://get.haskellstack.org | sh" args: creates:"/usr/local/bin/stack"- name: Configure Nginx copy: src:"{{ item }}" dest:"/etc/nginx/snippets/{{ haskell__app_name }}/{{ item }}" with_items: - haskell.conf notify: reload nginx- name: Create haskell serviceinSystemd template: src: haskell.service.j2 dest: /etc/systemd/system/{{ haskell__app_name}}-hs.service mode: 0644- name: Enable haskell serviceinSystemd systemd: name:"{{ haskell__app_name }}-hs" enabled:yesdaemon_reload:yesstate: started- name: Allow user to restart the application lineinfile: dest: /etc/sudoers state: present line:"{{ haskell__username }} ALL=(ALL) NOPASSWD: /bin/systemctl restart {{ haskell__app_name }}-hs"
Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to myPinkLetter!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse