Deploying Yesod applications with Keter
Keter is the
Yesod’s deployment system, fully featured and a joy
to use, but there are some pitfalls that the documentation doesn’t cover, and
that the user has to find out for her self; So I’ll try to give them away here
together with a walk-through tutorial.
Although Keter is flexible and general enough to be used with various kind of
applications and web frameworks, here I’m going to assume you’re using it to
deploy Yesod applications. Moreover, I’ll assume you’re using Yesod’s
scaffolding, as it is the preferred way to write production ready applications.
I’m also taking for granted that you’ve already installed on your server system
whatever DBMS that your Yesod app needs, and have also created the app’s
databases.
Installing Keter on the server
Keter binary
It is always advisable to compile on the development machine rather than the
production server, to avoid utilising its resources for building (specially
considering that GHC can make use of a fair amount of them). So, assuming the
architectures match, you can just install keter
on you local machine:
$ stack install keter
And then put the binary on the server (example.com):
$ scp ~/.local/bin/keter root@example.com:/root/
Keter user
It’s a good practice to have a dedicated keter user, so you don’t have to
deploy as root each time:
# useradd keter
# passwd keter
Directory tree
The directory tree needed on the server is as follows:
keter
├── bin
│ └── keter
├── etc
│ └── keter-config.yaml
├── incoming
└── app.keter
So create it, copy the binary to /opt/keter/bin
, and make sure
/opt/keter/incoming
it’s owned by the keter user (we’ll take care of the
keter-config.yaml
configuration later):
# mkdir -p /opt/keter /opt/keter/bin /opt/keter/etc /opt/keter/incoming
# cp /root/keter /opt/keter/bin
# touch /opt/keter/keter-config.yaml
# chown -R keter:keter /opt/keter/icoming
Init System
While you could just execute /opt/keter/bin/keter
directly, it’s better to
register it as a job in your Init System.
Sysmted (RedHat, Fedora, CentOS, Arch, openSUSE, etc)
Create a file /etc/systemd/system/keter.service
, with the contents:
[Unit]
Description=Keter
After=network.service
[Service]
Type=simple
ExecStart=/opt/keter/bin/keter /opt/keter/etc/keter-config.yaml
[Install]
WantedBy=multi-user.target
Enable the service:
$ sudo systemctl enable keter
Now you can start keter with (don’t do it just yet, as we still need to write
the keter configuration file):
$ sudo systemctl start keter
Upstart (Debian, Ubuntu, etc)
Create a file /etc/init/keter.con
, with the contents:
start on (net-device-up and local-filesystems and runlevel [2345])
stop on runlevel [016]
respawn
console output
exec /opt/keter/bin/keter /opt/keter/etc/keter-config.yaml
Now you can start keter with (don’t do it just yet, as we still need to write
the keter configuration file):
$ sudo start keter
Configuration
Server Side
The Keter configuration at /opt/keter/etc/keter-config.yaml
is pretty
straight forward:
root: ..
listeners:
# HTTP
- host: "*4" # Listen on all IPv4 hosts
port: 80
# HTTPS
#- host: "*4"
#port: 443
#key: key.pem
#certificate: certificate.pem
# env:
# key: value
The root
option points, as expected, to /opt/keter
.
Make sure to change the port
option if you’re reverse forwarding from a
fronted server like Nginx or Apache (more on this later).
If you’re serving your application over SSL (and you should), uncomment the
HTTPS section, then point the key
option to your privkey.pem
file, and the
certificate
option to your fullchain.pem
file.
The env
option, keeps pairs of keys and values. The main set of values
you’ll need here are your Database credentials. You’ve probably already
configured database credentials in the database
section in the
config/settings.yaml
file, so you’ll notice you need some environment
variables like MYSQL_USER
, MYSQL_PASSWORD
, etc. If you’re using
MySQL/MariaDB; Or PGUSER
, PGPASS
, etc. If you’re using PostgreSQL. You
get the idea.
This is how it will look like for a PostgreSQL Database where only the user and
password are different between the development and production servers (be sure
to keep the quotes).
env:
PGUSER: "user"
PGPASS: "password"
Yesod application side
The Keter configuration file for your Yesod application lives in
config/keter.yml
. Set user-edited
to true
, so you’re able to execute
yesod keter
later on.
Locate the copy-to
option and configure it to use the keter
user and your
server domain (or IP address):
copy-to: keter@example.com:/opt/keter/incoming/
This will allow you to deploy your application with:
$ stack -- exec yesod keter
Hosts Configuration
The most important part of the Keter configuration is perhaps the hosts
option of the webapp
stanza, the hosts you declare here are the ones that your
application is going to respond to. Unless you’re using a separate domain for
serving static files, be sure to keep the hosts
option of the static-files
stanza in sync with the webapp
one.
This one here is a pretty common error message when trying to deploy a Yesod
application (and failing miserably):
There is more than one reason for this, but the main one is that the domain name
or IP address doesn’t exactly match one of the hosts provided in the hosts
option.
If you’re serving only one application and using Keter as the main server
listening on port 80
, then having your domain name in hosts
will pretty much
suffice, BUT most of the time, even if your serving only one application, you’re
probably using a frontend server like Nginx or Apache, in which case you
have to consider the port the reverse proxy is pointing to.
Take for instance this Nginx reverse proxy configuration for an app that lives
on blog.example.com
server {
listen 80;
server_name blog.example.com;
location / {
proxy_pass http://127.0.0.1:4321;
}
With a Keter configuration that has:
listeners:
- host: "*4" # Listen on all IPv4 hosts
port: 4321
Then you have a problem. If you try to connect to http://blog.example.com
you’ll get the aforementioned error message, telling you that “127.0.0.1:4321,
is not recognized”. It makes sense if you think about it, Nginx will redirect
the connection to 127.0.0.1:4321
so Keter can handle it, but there is no
application that responds to 127.0.0.1:4321
, and notice the port number here,
as it is significant for Keter when trying to find a corresponding
application.
To fix this, we must allow our application to respond to 127.0.0.1:4321
as
well:
hosts:
- blog.example.com
- blog.example.com:4321
- 127.0.0.1:4321
Restart Ngnix and Keter on the server to allow the configuration to take
effect and redeploy the application:
$ stack -- exec yesod keter
Redirections
If you’re going to use the redirect
stanza to automatically redirect any
connection to, lets say example.com
to wwww.example.com
:
- type: redirect
hosts:
- example.com
actions:
- host: www.example.com
Then be completely sure to have www.example.com
in the hosts
option of the
webapp
stanza as well, failing to do this will take you to the same error
message.
Other sources of error
“Welcome to Keter”
If you’re still reaching this error:
Unfortunately, the same error message appears if an application that responds to
that host is actually found, but is failing to start.
Check the /opt/keter/log/app-yourapp/current.log
log file, chances are you
have changes in your persistent models that can’t be reflected in your database
without user intervention, so be sure to manually fix them the same way you have
to do in your development database.
Changes in new deployed version not taking effect
It is pretty common to forget changes in persistent models that need manual user
intervention after deploying, similarly to the error above, this will prevent
the app to start. If you currently have a version of your app working, Keter
will use it instead of the new one if it fails to start, so if the latest
deployed changes seem to not be taking effect, this can also be the source of
the problem.
Keter is the Yesod’s deployment system, fully featured and a joy to use, but there are some pitfalls that the documentation doesn’t cover, and that the user has to find out for her self; So I’ll try to give them away here together with a walk-through tutorial.
Although Keter is flexible and general enough to be used with various kind of applications and web frameworks, here I’m going to assume you’re using it to deploy Yesod applications. Moreover, I’ll assume you’re using Yesod’s scaffolding, as it is the preferred way to write production ready applications.
I’m also taking for granted that you’ve already installed on your server system whatever DBMS that your Yesod app needs, and have also created the app’s databases.
Installing Keter on the server
Keter binary
It is always advisable to compile on the development machine rather than the
production server, to avoid utilising its resources for building (specially
considering that GHC can make use of a fair amount of them). So, assuming the
architectures match, you can just install keter
on you local machine:
$ stack install keter
And then put the binary on the server (example.com):
$ scp ~/.local/bin/keter root@example.com:/root/
Keter user
It’s a good practice to have a dedicated keter user, so you don’t have to deploy as root each time:
# useradd keter
# passwd keter
Directory tree
The directory tree needed on the server is as follows:
keter
├── bin
│ └── keter
├── etc
│ └── keter-config.yaml
├── incoming
└── app.keter
So create it, copy the binary to /opt/keter/bin
, and make sure
/opt/keter/incoming
it’s owned by the keter user (we’ll take care of the
keter-config.yaml
configuration later):
# mkdir -p /opt/keter /opt/keter/bin /opt/keter/etc /opt/keter/incoming
# cp /root/keter /opt/keter/bin
# touch /opt/keter/keter-config.yaml
# chown -R keter:keter /opt/keter/icoming
Init System
While you could just execute /opt/keter/bin/keter
directly, it’s better to
register it as a job in your Init System.
Sysmted (RedHat, Fedora, CentOS, Arch, openSUSE, etc)
Create a file /etc/systemd/system/keter.service
, with the contents:
[Unit]
Description=Keter
After=network.service
[Service]
Type=simple
ExecStart=/opt/keter/bin/keter /opt/keter/etc/keter-config.yaml
[Install]
WantedBy=multi-user.target
Enable the service:
$ sudo systemctl enable keter
Now you can start keter with (don’t do it just yet, as we still need to write the keter configuration file):
$ sudo systemctl start keter
Upstart (Debian, Ubuntu, etc)
Create a file /etc/init/keter.con
, with the contents:
start on (net-device-up and local-filesystems and runlevel [2345])
stop on runlevel [016]
respawn
console output
exec /opt/keter/bin/keter /opt/keter/etc/keter-config.yaml
Now you can start keter with (don’t do it just yet, as we still need to write the keter configuration file):
$ sudo start keter
Configuration
Server Side
The Keter configuration at /opt/keter/etc/keter-config.yaml
is pretty
straight forward:
root: ..
listeners:
# HTTP
- host: "*4" # Listen on all IPv4 hosts
port: 80
# HTTPS
#- host: "*4"
#port: 443
#key: key.pem
#certificate: certificate.pem
# env:
# key: value
The root
option points, as expected, to /opt/keter
.
Make sure to change the port
option if you’re reverse forwarding from a
fronted server like Nginx or Apache (more on this later).
If you’re serving your application over SSL (and you should), uncomment the
HTTPS section, then point the key
option to your privkey.pem
file, and the
certificate
option to your fullchain.pem
file.
The env
option, keeps pairs of keys and values. The main set of values
you’ll need here are your Database credentials. You’ve probably already
configured database credentials in the database
section in the
config/settings.yaml
file, so you’ll notice you need some environment
variables like MYSQL_USER
, MYSQL_PASSWORD
, etc. If you’re using
MySQL/MariaDB; Or PGUSER
, PGPASS
, etc. If you’re using PostgreSQL. You
get the idea.
This is how it will look like for a PostgreSQL Database where only the user and password are different between the development and production servers (be sure to keep the quotes).
env:
PGUSER: "user"
PGPASS: "password"
Yesod application side
The Keter configuration file for your Yesod application lives in
config/keter.yml
. Set user-edited
to true
, so you’re able to execute
yesod keter
later on.
Locate the copy-to
option and configure it to use the keter
user and your
server domain (or IP address):
copy-to: keter@example.com:/opt/keter/incoming/
This will allow you to deploy your application with:
$ stack -- exec yesod keter
Hosts Configuration
The most important part of the Keter configuration is perhaps the hosts
option of the webapp
stanza, the hosts you declare here are the ones that your
application is going to respond to. Unless you’re using a separate domain for
serving static files, be sure to keep the hosts
option of the static-files
stanza in sync with the webapp
one.
This one here is a pretty common error message when trying to deploy a Yesod application (and failing miserably):
There is more than one reason for this, but the main one is that the domain name
or IP address doesn’t exactly match one of the hosts provided in the hosts
option.
If you’re serving only one application and using Keter as the main server
listening on port 80
, then having your domain name in hosts
will pretty much
suffice, BUT most of the time, even if your serving only one application, you’re
probably using a frontend server like Nginx or Apache, in which case you
have to consider the port the reverse proxy is pointing to.
Take for instance this Nginx reverse proxy configuration for an app that lives
on blog.example.com
server {
listen 80;
server_name blog.example.com;
location / {
proxy_pass http://127.0.0.1:4321;
}
With a Keter configuration that has:
listeners:
- host: "*4" # Listen on all IPv4 hosts
port: 4321
Then you have a problem. If you try to connect to http://blog.example.com
you’ll get the aforementioned error message, telling you that “127.0.0.1:4321,
is not recognized”. It makes sense if you think about it, Nginx will redirect
the connection to 127.0.0.1:4321
so Keter can handle it, but there is no
application that responds to 127.0.0.1:4321
, and notice the port number here,
as it is significant for Keter when trying to find a corresponding
application.
To fix this, we must allow our application to respond to 127.0.0.1:4321
as
well:
hosts:
- blog.example.com
- blog.example.com:4321
- 127.0.0.1:4321
Restart Ngnix and Keter on the server to allow the configuration to take effect and redeploy the application:
$ stack -- exec yesod keter
Redirections
If you’re going to use the redirect
stanza to automatically redirect any
connection to, lets say example.com
to wwww.example.com
:
- type: redirect
hosts:
- example.com
actions:
- host: www.example.com
Then be completely sure to have www.example.com
in the hosts
option of the
webapp
stanza as well, failing to do this will take you to the same error
message.
Other sources of error
“Welcome to Keter”
If you’re still reaching this error:
Unfortunately, the same error message appears if an application that responds to that host is actually found, but is failing to start.
Check the /opt/keter/log/app-yourapp/current.log
log file, chances are you
have changes in your persistent models that can’t be reflected in your database
without user intervention, so be sure to manually fix them the same way you have
to do in your development database.
Changes in new deployed version not taking effect
It is pretty common to forget changes in persistent models that need manual user intervention after deploying, similarly to the error above, this will prevent the app to start. If you currently have a version of your app working, Keter will use it instead of the new one if it fails to start, so if the latest deployed changes seem to not be taking effect, this can also be the source of the problem.