Deploy Fano application inside Docker container

Prerequisite

You must have a working Docker and Docker Compose installation.

It is assumed that your current user is in the group docker. If you are not in the docker group, you have to prefix all Docker commands with sudo or run them with elevated privileges (root).

Deploy Fano CLI-generated Project with Docker

Fano CLI generates docker-compose.yaml file during creation of CGI, FastCGI, SCGI and uwsgi project since v1.13.0.

This means you can skip the steps explained in the sections below and simply compile the application and run it with docker-compose up.

$ fanocli --project-fcgi=my-fcgi.fano
$ cd my-fcgi.fano
$ fanocli --controller=Home --route=/
$ ./build.sh
$ docker-compose up

Open the IP address of the Fano application container to access it. Read Get IP Address of Fano Application Docker Container for more information.

Deploy Fano CGI Application with Docker

Create Dockerfile

Create file name Dockerfile in project root directory with content as shown below

FROM httpd:2.4

This tells Docker to create container image with Apache 2.4. This is Debian-based image with common Apache modules installed.

Now append lines below. We make sure that mod_cgi, mod_cgid and mod_rewrite modules are loaded by modifying main Apache configuration like so.

RUN sed -i \
        -e 's/^#\(LoadModule .*mod_cgi.so\)/\1/' \
        -e 's/^#\(LoadModule .*mod_cgid.so\)/\1/' \
        -e 's/^#\(LoadModule .*mod_rewrite.so\)/\1/' \
        -e 's/^#\(Include .*httpd-vhosts.conf\)/\1/' \
        -e 's/^#ServerName.*/ServerName fano-app/' \
        /usr/local/apache2/conf/httpd.conf

If it is not clear to you, regular expression replaces #LoadModule mod_cgi to become LoadModule mod_cgi, effectively telling Apache to load mod_cgi.

We also tell Apache to include any additional configurations in the httpd-vhosts.conf file and set the ServerName configuration.

Append the line below to copy the vhost.example file to httpd-vhosts.conf inside the container. We will create vhost.example later.

COPY ./vhost.example /usr/local/apache2/conf/extra/httpd-vhosts.conf

Remove index.html as we do not need it, but this is an optional step.

RUN rm /usr/local/apache2/htdocs/index.html

Now add below it

COPY ./config/ /usr/local/apache2/config
COPY ./resources/ /usr/local/apache2/resources
COPY ./storages/ /usr/local/apache2/storages
COPY ./public/ /usr/local/apache2/htdocs/
COPY ./public/app.cgi /usr/local/apache2/htdocs/app.cgi

Which copy all runtime files needed by application such as configuration, HTML templates, CSS and JavaScript files.

So final Dockerfile will be

FROM httpd:2.4

RUN sed -i \
        -e 's/^#\(LoadModule .*mod_cgi.so\)/\1/' \
        -e 's/^#\(LoadModule .*mod_cgid.so\)/\1/' \
        -e 's/^#\(LoadModule .*mod_rewrite.so\)/\1/' \
        -e 's/^#\(Include .*httpd-vhosts.conf\)/\1/' \
        -e 's/^#ServerName.*/ServerName fano-app/' \
        /usr/local/apache2/conf/httpd.conf

COPY ./vhost.example /usr/local/apache2/conf/extra/httpd-vhosts.conf

RUN rm /usr/local/apache2/htdocs/index.html

COPY ./config/ /usr/local/apache2/config
COPY ./resources/ /usr/local/apache2/resources
COPY ./storages/ /usr/local/apache2/storages
COPY ./public/ /usr/local/apache2/htdocs/
COPY ./public/app.cgi /usr/local/apache2/htdocs/app.cgi

Create virtual host configuration

Create a file name vhost.example in same directory as Dockerfile with content like so

<VirtualHost *:80>
    <Directory "/usr/local/apache2/htdocs">
        Options +ExecCGI
        AllowOverride FileInfo Indexes
        Require all granted
        DirectoryIndex app.cgi
        AddHandler cgi-script .cgi
        <IfModule mod_rewrite.c>
            RewriteEngine On

            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ app.cgi [L]
        </IfModule>
    </Directory>
</VirtualHost>

Run application in Docker container

Run build.sh to compile application first and then from inside directory where Dockerfile resides, run

$ docker build -t fano-app .

This builds docker image with tag fano-app and may take a while.

After that run

$ docker run fano-app

Open IP address of Fano application container to access application. Read Get IP address of Fano application docker container for more information.

Run with docker-compose

We can take it further by using docker-compose to run application. Create new file name docker-compose.yaml in same directory as Dockerfile.

version: "2"
services:
    fano:
        build: .

Now we can run with

$ docker-compose up

To stop the application, run

$ docker-compose down

Deploy Fano FastCGI Application with Docker

Create Dockerfile

If you create FastCGI project using --project-fcgid

$ fanocli --project-fcgid=testfano

To deploy using Docker, we need to install mod_fcgid manually. This module is not installed by default in httpd:2.4 image.

FROM:home:24

#-----------------------------------
# Install mod_fcgid
#-----------------------------------
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    libapache2-mod-fcgid

Following mod_fcgid installation, we tells Apache to load module.

#-----------------------------------
# Enable mod_fcgid mod_rewrite and
# additional virtual host config
#-----------------------------------
RUN sed -i \
        -e 's/^#\(LoadModule .*mod_rewrite.so\)/\1/' \
        -e 's/^#\(Include .*httpd-vhosts.conf\)/\1/' \
        -e '/LoadModule .*mod_rewrite.so/a LoadModule fcgid_module /usr/lib/apache2/modules/mod_fcgid.so' \
        -e 's/^#ServerName.*/ServerName fano-app/' \
        /usr/local/apache2/conf/httpd.conf

The rest of Dockerfile is same as Dockerfile for CGI above.

Deploy Fano FastCGI Application with Docker

Create Dockerfile

For FastCGI that runs as separate server via reverse-proxy module, i.e, using --project-fcgi, you can run Fano application and Apache as separate docker container and run it with docker-compose.

$ fanocli --project-fcgi=testfano

For that we need to create two Dockerfiles for Fano application and Apache.

Dockerfile for Fano application

Dockerfile for Fano application pulls Ubuntu image and copies binary executable, configuration file, HTML templates etc.

Last, we tells Docker to execute application and listen on 0.0.0.0:7704. Parameter --host=0.0.0.0 is required, otherwise our application will only listen on 127.0.0.1. Because Apache will act as reverse-proxy server on different container, it will not be able to connect to Fano application without --host=0.0.0.0.

FROM ubuntu

COPY ./config/ /usr/local/fano/config
COPY ./resources/ /usr/local/fano/resources
COPY ./bin/app.cgi /usr/local/fano/app.cgi
CMD ["/usr/local/fano/app.cgi", "--host=0.0.0.0", "--port=7704"]

We name this Dockerfile as fano_dockerfile.

Dockerfile for Apache reverse proxy server

Dockerfile for Apache is similar to above Dockerfile for CGI, except it sets Apache as FastCGI reverse proxy server that connect to our application. This is done by tells Apache to load mod_proxy and mod_proxy_fcgi.

FROM httpd:2.4

#-----------------------------------
# Enable mod_proxy_fcgi mod_rewrite and
# additional virtual host config
#-----------------------------------
RUN sed -i \
        -e 's/^#\(LoadModule .*mod_proxy.so\)/\1/' \
        -e 's/^#\(LoadModule .*mod_proxy_fcgi.so\)/\1/' \
        -e 's/^#\(LoadModule .*mod_rewrite.so\)/\1/' \
        -e 's/^#\(Include .*httpd-vhosts.conf\)/\1/' \
        -e 's/^#ServerName.*/ServerName fano-app/' \
        /usr/local/apache2/conf/httpd.conf && \
    rm /usr/local/apache2/htdocs/index.html

#-----------------------------------
# set default virtual host config
#-----------------------------------
COPY ./vhost.example /usr/local/apache2/conf/extra/httpd-vhosts.conf

#-----------------------------------
# copy public assets (images, css, js)
#-----------------------------------
COPY ./public/ /usr/local/apache2/htdocs

We copy vhost.example into container virtual host configuraton. We will create it latter.

We name this Dockerfile as httpd_dockerfile.

Create virtual host configuration

We create new file vhost.example to set FastCGI reverse proxy. fcgi://fano:7704 line tells Apache to forward FastCGI data to our application container identified as fano (This is service we define in docker-compose.yaml below).

<VirtualHost *:80>
    ProxyRequests Off
    ProxyPassMatch "/css|js|images|img|plugins|bower_components(.*)|webfonts" !
    ProxyPassMatch ^/(.*)$ "fcgi://fano:7704"
</VirtualHost>

Create docker-compose configuration.

To simplify running both application, we use docker-compose with configuration like so

version: "2"
services:
    fano:
        build:
            context: .
            dockerfile: fano_dockerfile
        ports:
            - "7704:7704"
    apache:
        build:
            context: .
            dockerfile: httpd_dockerfile
        depends_on:
            - fano

Here we tells docker-compose to build two services from two dockerfiles we previously created. We also forward traffic on port 7704 so that Apache container can connect it.

Do not forget to save it as docker-compose.yaml.

Run FastCGI application with docker-compose

To run application and Apache, run build.sh script first and then,

$ docker-compose up

Open IP address of Fano application container to access it. Read Get IP address of Fano application docker container for more information.

Deploy Fano SCGI Application with Docker

This is similar to FastCGI reverse proxy configuration above, except httpd_dockerfile loads mod_proxy_scgi and vhost.example contains scgi://fano:7704 line.

Deploy Fano uwsgi Application with Docker

This is similar to FastCGI reverse proxy configuration above, except httpd_dockerfile loads mod_proxy_uwsgi and vhost.example contains uwsgi://fano:7704 line.

Get IP Address of Fano Application Docker Container

To access application, we need to get IP address of Apache container image. Run

$ docker network ls

It lists all Docker available networks. For example,

$ docker network ls
NETWORK ID     NAME                            DRIVER    SCOPE
f520b12e38fb   testfano_default                bridge    local

Network ID and Name may be different on your system. If our application is in testfano directory, it is listed as testfano_default by default.

To get IP address

$ docker network inspect testfano_default

It shows network information on that container. For example,

$ docker network inspect testfano_default
[
   {
        "Name": "testfano_default",
        "Id": "f520b12e38fb0c5f1bab61922b6a9082bd9f2323904b5870a985d04c78d81bfa",
        "Created": "2023-03-22T05:57:55.059000134+07:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.20.0.0/16",
                    "Gateway": "172.20.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "8a24d3bfb7686432ae3fdd8b48b30aa019a8b78cce6f5bbba00349f777f7bbaa": {
                "Name": "testfano_apache_1",
                "EndpointID": "60bfaa82b8e7714a7de85aef6426b6874019862e7137929ce1313adcd9a0be11",
                "MacAddress": "02:42:ac:18:00:03",
                "IPv4Address": "172.20.0.3/16",
                "IPv6Address": ""
            },
            "b888c7aac3dc0bec9248ba9d6e74f2f0da89e0db13e331103c461d98c50650cf": {
                "Name": "testfano_fano_1",
                "EndpointID": "38f929d0f99cc430c20e0bb736f292ba9773fa416eaa13852c006298579f7aad",
                "MacAddress": "02:42:ac:18:00:02",
                "IPv4Address": "172.20.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "testfano",
            "com.docker.compose.version": "1.29.2"
        }
    }
]

Actual values may be different on your system.

Find IP address for Apache container. In above example, it is testfano_apache_1, with IP 172.20.0.3/16. Open browser and visit http://172.20.0.3 to access our application.

Explore more