Skip to content

Commit 241d406

Browse files
committed
building & deployment++ (mostly from Cookbook)
1 parent 259e769 commit 241d406

File tree

2 files changed

+144
-168
lines changed

2 files changed

+144
-168
lines changed
Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,96 @@
11
+++
2-
title = "Building a binary"
2+
title = "Running and Building"
33
weight = 150
44
+++
55

6+
7+
### Running the application from source
8+
9+
{{% notice info %}}
10+
See the tutorial.
11+
{{% /notice %}}
12+
13+
To run our Lisp code from source, as a script, we can use the `--load`
14+
switch from our implementation.
15+
16+
We must ensure:
17+
18+
- to load the project's .asd system declaration (if any)
19+
- to install the required dependencies (this demands we have installed Quicklisp previously)
20+
- and to run our application's entry point.
21+
22+
So, the recipe to run our project from sources can look like this (you can find such a recipe in our project generator):
23+
24+
~~~lisp
25+
;; run.lisp
26+
27+
(load "myproject.asd")
28+
29+
(ql:quickload "myproject")
30+
31+
(in-package :myproject)
32+
(handler-case
33+
;; The START function starts the web server.
34+
(myproject::start :port (ignore-errors (parse-integer (uiop:getenv "PROJECT_PORT"))))
35+
(error (c)
36+
(format *error-output* "~&An error occured: ~a~&" c)
37+
(uiop:quit 1)))
38+
~~~
39+
40+
In addition we have allowed the user to set the application's port
41+
with an environment variable.
42+
43+
We can run the file like so:
44+
45+
sbcl --load run.lisp
46+
47+
After loading the project, the web server is started in the
48+
background. We are offered the usual Lisp REPL, from which we can
49+
interact with the running application.
50+
51+
We can also connect to the running application from our preferred
52+
editor, from home, and compile the changes in our editor to the
53+
running instance. See the following section:
54+
[connecting to a remote lisp image](https://lispcookbook.github.io/cl-cookbook/debugging.html#remote-debugging) on the Cookbook.
55+
56+
57+
### Building a self-contained executable
58+
59+
{{% notice info %}}
60+
See the tutorial.
61+
{{% /notice %}}
62+
63+
As for all Common Lisp applications, we can bundle our web app in one
64+
single executable, including the assets. It makes deployment very
65+
easy: copy it to your server and run it.
66+
67+
```
68+
$ ./my-web-app
69+
Hunchentoot server is started.
70+
Listening on localhost:9003.
71+
```
72+
73+
See this recipe on [scripting#for-web-apps](scripting.html#for-web-apps).
74+
75+
As for any executable, you need this in your .asd file:
76+
77+
~~~lisp
78+
:build-operation "program-op" ;; leave as is
79+
:build-pathname "<binary-name>"
80+
:entry-point "<my-package:main-function>"
81+
~~~
82+
83+
and you build the binary with `(asdf:make :myproject)`.
84+
85+
However, you might find that as soon as you start your app, its
86+
stops. That happens because the server thread is started in the background, and nothing tells the binary to wait for it. We can simply sleep (for a large-enough amount of time).
87+
88+
89+
~~~lisp
90+
(defun main ()
91+
(start-app :port 9003) ;; our start-app
92+
;; keep the binary busy in the foreground, for binaries and SystemD.
93+
(sleep most-positive-fixnum))
94+
~~~
95+
96+
If you want to learn more, see… [the Cookbook: scripting, command line arguments, executables](https://lispcookbook.github.io/cl-cookbook/scripting.html).

content/building-blocks/deployment.md

Lines changed: 52 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -4,142 +4,14 @@ weight = 200
44
+++
55

66

7-
How to run, build, test, deploy and monitor a Common Lisp web app.
7+
How to deploy and monitor a Common Lisp web app?
88

9-
### Running the application from source
9+
{{% notice info %}}
10+
We are re-using content we contributed to the Cookbook.
11+
{{% /notice %}}
1012

11-
To run our Lisp code from source, as a script, we can use the `--load`
12-
switch from our implementation.
1313

14-
We must ensure:
15-
16-
- to load the project's .asd system declaration (if any)
17-
- to install the required dependencies (this demands we have installed Quicklisp previously)
18-
- and to run our application's entry point.
19-
20-
So, the recipe to run our project from sources can look like this (you can find such a recipe in our project generator):
21-
22-
~~~lisp
23-
;; run.lisp
24-
25-
(load "myproject.asd")
26-
27-
(ql:quickload "myproject")
28-
29-
(in-package :myproject)
30-
(handler-case
31-
;; The START function starts the web server.
32-
(myproject::start :port (ignore-errors (parse-integer (uiop:getenv "PROJECT_PORT"))))
33-
(error (c)
34-
(format *error-output* "~&An error occured: ~a~&" c)
35-
(uiop:quit 1)))
36-
~~~
37-
38-
In addition we have allowed the user to set the application's port
39-
with an environment variable.
40-
41-
We can run the file like so:
42-
43-
sbcl --load run.lisp
44-
45-
After loading the project, the web server is started in the
46-
background. We are offered the usual Lisp REPL, from which we can
47-
interact with the running application.
48-
49-
We can also connect to the running application from our preferred
50-
editor, from home, and compile the changes in our editor to the
51-
running instance. See the following section:
52-
[connecting to a remote lisp image](https://lispcookbook.github.io/cl-cookbook/debugging.html#remote-debugging) on the Cookbook.
53-
54-
55-
### Building a self-contained executable
56-
57-
As for all Common Lisp applications, we can bundle our web app in one
58-
single executable, including the assets. It makes deployment very
59-
easy: copy it to your server and run it.
60-
61-
```
62-
$ ./my-web-app
63-
Hunchentoot server is started.
64-
Listening on localhost:9003.
65-
```
66-
67-
See this recipe on [scripting#for-web-apps](scripting.html#for-web-apps).
68-
69-
As for any executable, you need this in your .asd file:
70-
71-
~~~lisp
72-
:build-operation "program-op" ;; leave as is
73-
:build-pathname "<binary-name>"
74-
:entry-point "<my-package:main-function>"
75-
~~~
76-
77-
and you build the binary with `(asdf:make :myproject)`.
78-
79-
However, you might find that as soon as you start your app, its
80-
stops. That happens because the server thread is started in the background, and nothing tells the binary to wait for it. Let's do it by putting the server thread in the foreground.
81-
82-
You need the `bordeaux-threads` library, which as the `bt` global nickname.
83-
84-
~~~lisp
85-
(defun main ()
86-
(start-app :port 9003) ;; our start-app
87-
;; let the webserver run.
88-
;; note: we hardcoded our webserver name, "hunchentoot" here.
89-
(handler-case (bt:join-thread (find-if (lambda (th)
90-
(search "hunchentoot" (bt:thread-name th)))
91-
(bt:all-threads)))
92-
;; Catch a user's C-c
93-
(#+sbcl sb-sys:interactive-interrupt
94-
#+ccl ccl:interrupt-signal-condition
95-
#+clisp system::simple-interrupt-condition
96-
#+ecl ext:interactive-interrupt
97-
#+allegro excl:interrupt-signal
98-
() (progn
99-
(format *error-output* "Aborting.~&")
100-
(clack:stop *server*)
101-
(uiop:quit)))
102-
(error (c) (format t "Woops, an unknown error occured:~&~a~&" c))))
103-
~~~
104-
105-
If you want to learn more, see… [the Cookbook: scripting, command line arguments, executables](https://lispcookbook.github.io/cl-cookbook/scripting.html).
106-
107-
108-
<!-- ### Multi-platform delivery with Electron -->
109-
110-
<!-- [Ceramic](https://ceramic.github.io/) makes all the work for us. -->
111-
112-
<!-- It is as simple as this: -->
113-
114-
<!-- ~~~lisp -->
115-
<!-- ;; Load Ceramic and our app -->
116-
<!-- (ql:quickload '(:ceramic :our-app)) -->
117-
118-
<!-- ;; Ensure Ceramic is set up -->
119-
<!-- (ceramic:setup) -->
120-
<!-- (ceramic:interactive) -->
121-
122-
<!-- ;; Start our app (here based on the Lucerne framework) -->
123-
<!-- (lucerne:start our-app.views:app :port 8000) -->
124-
125-
<!-- ;; Open a browser window to it -->
126-
<!-- (defvar window (ceramic:make-window :url "http://localhost:8000/")) -->
127-
128-
<!-- ;; start Ceramic -->
129-
<!-- (ceramic:show-window window) -->
130-
<!-- ~~~ -->
131-
132-
<!-- and we can ship this on Linux, Mac and Windows. -->
133-
134-
<!-- There is more: -->
135-
136-
<!-- > Ceramic applications are compiled down to native code, ensuring both performance and enabling you to deliver closed-source, commercial applications. -->
137-
138-
<!-- Thus, no need to minify our JS. -->
139-
140-
## Deployment
141-
142-
### Deploying manually
14+
## Deploying manually
14315

14416
We can start our executable in a shell and send it to the background (`C-z bg`), or run it inside a `tmux` session. These are not the best but hey, it works©.
14517

@@ -160,7 +32,7 @@ Here's [a cheatsheet](https://tmuxcheatsheet.com/) that was handy.
16032
Unfortunately, if your app crashes or if your server is rebooted, your apps will be stopped. We can do better.
16133

16234

163-
### SystemD: daemonizing, restarting in case of crashes, handling logs
35+
## SystemD: daemonizing, restarting in case of crashes, handling logs
16436

16537
This is actually a system-specific task. See how to do that on your system.
16638

@@ -209,7 +81,7 @@ to enable it:
20981
sudo systemctl enable my-app.service
21082

21183

212-
### With Docker
84+
## With Docker
21385

21486
There are several Docker images for Common
21587
Lisp. For example:
@@ -223,17 +95,52 @@ is CentOs based and contains the source for building a Quicklisp based
22395
Common Lisp application as a reproducible docker image using OpenShift's
22496
source-to-image.
22597

98+
### Running behind Nginx
99+
100+
There is nothing CL-specific to run your Lisp web app behind Nginx. Here's an example to get you started.
101+
102+
We suppose you are running your Lisp app on a web server, with the IP
103+
address 1.2.3.4, on the port 8001. Nothing special here. We want to
104+
access our app with a real domain name (and eventuall benefit of other
105+
Nginx's advantages, such as rate limiting etc). We bought our domain
106+
name and we created a DNS record of type A that links the domain name
107+
to the server's IP address.
108+
109+
We must configure our server with Nginx to tell it that all
110+
connections coming from "your-domain-name.org", on port 80, are to be
111+
sent to the Lisp app running locally.
112+
113+
Create a new file: `/etc/nginx/sites-enabled/my-lisp-app.conf` and add this proxy directive:
114+
115+
~~~lisp
116+
server {
117+
listen www.your-domain-name.org:80;
118+
server_name your-domain-name.org www.your-domain-name.org; # with and without www
119+
location / {
120+
proxy_pass http://1.2.3.4:8001/;
121+
}
122+
123+
# Optional: serve static files with nginx, not the Lisp app.
124+
location /files/ {
125+
proxy_pass http://1.2.3.4:8001/files/;
126+
}
127+
}
128+
~~~
226129

227-
### With Guix
130+
Note that on the proxy_pass directive: `proxy_pass
131+
http://1.2.3.4:8001/;` we are using our server's public IP
132+
address. Often, your Lisp webserver such as Hunchentoot directly
133+
listens on it. You might want, for security reasons, to run the Lisp
134+
app on localhost.
228135

229-
[GNU Guix](https://www.gnu.org/software/guix/) is a transactional
230-
package manager, that can be installed on top of an existing OS, and a
231-
whole distro that supports declarative system configuration. It allows
232-
to ship self-contained tarballs, which also contain system
233-
dependencies. For an example, see the [Nyxt browser](https://github.com/atlas-engineer/nyxt/).
136+
Reload nginx (send the "reload" signal):
234137

138+
$ nginx -s reload
235139

236-
### Deploying on Heroku and other services
140+
and that's it: you can access your Lisp app from the outside through `http://www.your-domain-name.org`.
141+
142+
143+
## Deploying on Heroku and other services
237144

238145
See [heroku-buildpack-common-lisp](https://gitlab.com/duncan-bayne/heroku-buildpack-common-lisp) and the [Awesome CL#deploy](https://github.com/CodyReichert/awesome-cl#deployment) section for interface libraries for Kubernetes, OpenShift, AWS, etc.
239146

@@ -244,30 +151,8 @@ See [Prometheus.cl](https://github.com/deadtrickster/prometheus.cl)
244151
for a Grafana dashboard for SBCL and Hunchentoot metrics (memory,
245152
threads, requests per second,…).
246153

247-
## Connecting to a remote Lisp image
248-
249-
This this section: [debugging#remote-debugging](https://lispcookbook.github.io/cl-cookbook/debugging.html#remote-debugging) on the Cookbook.
250-
251-
## Hot reload
154+
See [cl-sentry-client](https://github.com/mmontone/cl-sentry-client/) for error reporting.
252155

253-
This is an example from [Quickutil](https://github.com/stylewarning/quickutil/blob/master/quickutil-server/). It is actually an automated version of the precedent section.
254-
255-
It has a Makefile target:
256-
257-
```lisp
258-
hot_deploy:
259-
$(call $(LISP), \
260-
(ql:quickload :quickutil-server) (ql:quickload :swank-client), \
261-
(swank-client:with-slime-connection (conn "localhost" $(SWANK_PORT)) \
262-
(swank-client:slime-eval (quote (handler-bind ((error (function continue))) \
263-
(ql:quickload :quickutil-utilities) (ql:quickload :quickutil-server) \
264-
(funcall (symbol-function (intern "STOP" :quickutil-server))) \
265-
(funcall (symbol-function (intern "START" :quickutil-server)) $(start_args)))) conn)) \
266-
$($(LISP)-quit))
267-
```
156+
## Connecting to a remote Lisp image (Swank server)
268157

269-
It has to be run on the server (a simple fabfile command can call this
270-
through ssh). Beforehand, a `fab update` has run `git pull` on the
271-
server, so new code is present but not running. It connects to the
272-
local swank server, loads the new code, stops and starts the app in a
273-
row.
158+
See this Cookbook section: [debugging#remote-debugging](https://lispcookbook.github.io/cl-cookbook/debugging.html#remote-debugging).

0 commit comments

Comments
 (0)