Deployment Elixir's mix release on dedicated server with Nginx

Their community, I have a working app. i decided to deploy it on a dedicated server via “Elixir mix release”. I don’t want to operate with Tools like Distillery, edeliver neither 3rd parties like Render, Gigalixir, Heroku etc…
i completed all the main steps:

  • setting phoenix with NGINX
  • Getting a certificate using Let’s Encrypt and make it https
  • Handling application’s secrets
  • Compiling application’s assets
  • tasks for DB migrations
  • assembling the release ($ MIX_ENV=prod mix release)
  • Starting server in production (SSL) ===> odylight.ci
    But when i interacted with the domain address… it didn’t work.

So i want to know if there is a way to achieve it… maybe i missed important part!
The project itself: https://bitbucket.org/persorg with all files.

CONFIG:

Ubuntu 16.04.6 LTS‬
phoenix 1.4.15
Elixir 1.10.2
Erlang/OTP 22
PostgreSQL 10.12
Node v12.16.1

NGINX conf:

#ATTENTION!
#
#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY,
#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED.
server {
        listen dedicated_ip:443 ssl http2;

        server_name odylight.ci;
        server_name www.odylight.ci;
        server_name ipv4.odylight.ci;

        ssl_certificate             /opt/psa/var/certificates/scf2i3Lbn;
        ssl_certificate_key         /opt/psa/var/certificates/scf2i3Lbn;

        error_page 400 "/error_docs/bad_request.html";
        error_page 401 "/error_docs/unauthorized.html";
        error_page 403 "/error_docs/forbidden.html";
        error_page 404 "/error_docs/not_found.html";
        error_page 500 "/error_docs/internal_server_error.html";
        error_page 405 "/error_docs/method_not_allowed.html";
        error_page 406 "/error_docs/not_acceptable.html";
        error_page 407 "/error_docs/proxy_authentication_required.html";
        error_page 412 "/error_docs/precondition_failed.html";
        error_page 414 "/error_docs/request_uri_too_long.html";
        error_page 415 "/error_docs/unsupported_media_type.html";
        error_page 501 "/error_docs/not_implemented.html";
        error_page 502 "/error_docs/bad_gateway.html";
        error_page 503 "/error_docs/maintenance.html";

        location /error_docs {
                root "/var/www/vhosts/odylight.ci";
        }

        client_max_body_size 128m;

        proxy_read_timeout 120;

        root "/var/www/vhosts/odylight.ci/httpdocs";
        access_log "/var/www/vhosts/system/odylight.ci/logs/proxy_access_ssl_log";
        error_log "/var/www/vhosts/system/odylight.ci/logs/proxy_error_log";

        #extension letsencrypt begin
        location ^~ /.well-known/acme-challenge/ {
                root /var/www/vhosts/default/htdocs;

                types { }
                default_type text/plain;

                satisfy any;
                auth_basic off;
                allow all;

                location ~ ^/\.well-known/acme-challenge.*/\. {
                        deny all;
                }
        }
        #extension letsencrypt end
        
        location ~ /\.ht {
                deny all;
        }

        location ~ /$ {
                index "index.html" "index.cgi" "index.pl" "index.php" "index.xhtml" "index.htm" "index.shtml";
        }

        location / {
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $host;
                proxy_set_header X-Forwarded-Server $host;
                proxy_set_header X-Forwarded-Proto https;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_redirect off;
                proxy_set_header X-Real-IP $remote_addr;

                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_http_version 1.1;
                proxy_pass http://127.0.0.1:4000;
        }
        add_header X-Powered-By PleskLin;

}
server {
        listen dedicated_ip:80;

        server_name odylight.ci;
        server_name www.odylight.ci;
        server_name ipv4.odylight.ci;

        error_page 400 "/error_docs/bad_request.html";
        error_page 401 "/error_docs/unauthorized.html";
        error_page 403 "/error_docs/forbidden.html";
        error_page 404 "/error_docs/not_found.html";
        error_page 500 "/error_docs/internal_server_error.html";
        error_page 405 "/error_docs/method_not_allowed.html";
        error_page 406 "/error_docs/not_acceptable.html";
        error_page 407 "/error_docs/proxy_authentication_required.html";
        error_page 412 "/error_docs/precondition_failed.html";
        error_page 414 "/error_docs/request_uri_too_long.html";
        error_page 415 "/error_docs/unsupported_media_type.html";
        error_page 501 "/error_docs/not_implemented.html";
        error_page 502 "/error_docs/bad_gateway.html";
        error_page 503 "/error_docs/maintenance.html";

        location /error_docs {
                root "/var/www/vhosts/odylight.ci";
        }

        client_max_body_size 128m;

        proxy_read_timeout 120;

        location / {
                return 301 https://$host$request_uri;
        }
}
1 Like

Does your application start? What does your endpoint config look like? Have you server: true or what that setting was called?

Can you see anything in the logs?

Did you build your release on a Ubuntu 16.04 as well?

yes before deploying to the dedicated server…i tested it localy, everything in both environement worked.

local test:

$ PORT=4001 MIX_ENV=prod mix phx.server
[info] Running MyApp.Endpoint with Cowboy on http://example.com
....... some other line.....

production result:

$ MIX_ENV=prod mix release
Generated persorg app
* assembling persorg-0.1.0 on MIX_ENV=prod
* using config/releases.exs to configure the release at runtime
* skipping elixir.bat for windows (bin/elixir.bat not found in the Elixir installation)
* skipping iex.bat for windows (bin/iex.bat not found in the Elixir installation)

Release created at _build/prod/rel/persorg!

    # To start your system
    _build/prod/rel/persorg/bin/persorg start

Once the release is running:

    # To connect to it remotely
    _build/prod/rel/persorg/bin/persorg remote

    # To stop it gracefully (you may also send SIGINT/SIGTERM)
    _build/prod/rel/persorg/bin/persorg stop

To list all commands:

    _build/prod/rel/persorg/bin/persorg
..................................
$ _build/prod/rel/persorg/bin/persorg start
[info] Running PersorgWeb.Endpoint with cowboy 2.7.0 at :::4000 (http)
[info] Access PersorgWeb.Endpoint at http://odylight.ci

releases.exs

# In this file, we load production configuration and secrets
# from environment variables. You can also hardcode secrets,
# although such is generally not recommended and you have to
# remember to add this file to your .gitignore.
import Config

database_url =
  System.get_env("DATABASE_URL") ||
    raise """
    environment variable DATABASE_URL is missing.
    For example: ecto://USER:PASS@HOST/DATABASE
    """

config :persorg, Persorg.Repo,
  # ssl: true,
  url: database_url,
  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

secret_key_base =
  System.get_env("SECRET_KEY_BASE") ||
    raise """
    environment variable SECRET_KEY_BASE is missing.
    You can generate one by calling: mix phx.gen.secret
    """

config :persorg, PersorgWeb.Endpoint,
  http: [
    port: String.to_integer(System.get_env("PORT") || "4000"),
    transport_options: [socket_opts: [:inet6]]
  ],
  secret_key_base: secret_key_base

# ## Using releases (Elixir v1.9+)
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start each relevant endpoint:
#
config :persorg, PersorgWeb.Endpoint, server: true
#
# Then you can assemble a release by calling `mix release`.
# See `mix help release` for more information.

Can you curl the running release? curl localhost:4000?

$ curl localhost:4000
curl: (7) Failed to connect to localhost port 4000: Connection refused

But it’s correctly started and listening?

netstat -lnpt | grep 4000

Also just a precautionary question, is there a container service involved?

i did :

$ nmap -Pn odylight.ci -p4000
.............................
PORT     STATE  SERVICE
4000/tcp closed remoteanything

typo netstat -lntp not netstat -lnpt | grep 4000 shows nothing.

But the application is running while you do that?

Because no output from netstat -l means that nothing is listening.

And again, are there any containers involved?

yes i saw that nothing is listening. the app is not running, i checked my nginx.conf again.
No there is no containers involved… Docker is installed but services are not used, no ports used.

So, when the app is running in foreground mode, is something listening then? Also does curl work then? After we have confirmed the application is reachable locally, we can get into configuring the nginx.

I understood the problem. It was not in any part of the elixir release packaging process. But I needed to know how to configure NGINX correctly. When using Plesk Obsedian, setting the nginx proxy configuration directly in your domain / manualy, trigs error, even if nginx -t succeed. Additionals nginx directives should be set via UI panel: Apache & Nginx settings > additional nginx directives.

PLESK OBSEDIAN

  1. use location ~ / {} instead of location / {}
location ~ / {
	allow all;

	# Proxy Headers
	proxy_http_version 1.1;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header Host $http_host;
	proxy_set_header X-Cluster-Client-Ip $remote_addr;

	# The Important Websocket Bits!
	proxy_set_header Upgrade $http_upgrade;
	proxy_set_header Connection "upgrade";

	proxy_pass http://127.0.0.1:4000;
}
  1. omitting upstreams
upstream phoenix {
  server 127.0.0.1:4000 max_fails=5 fail_timeout=60s;
}

I understood through a plesk forum: upstreams must be declared in nginx http{ } block, but plesk additional nginx directives are added in the server {} block. So that’s probably why it wasn’t working.

  • prevent the usage of the upstream directive (which in my case would/should not be necessary) with plesk architecture.

NB: Another recommendation: create new configuration files in /etc/nginx/conf.d instead of editing nginx.conf as i used to.
All .conf files added in conf.d are automatically included in nginx.conf.
conf== threads

  1. Now it works ===> odylight.ci
$ _build/prod/rel/persorg/bin/persorg start
[info] Running PersorgWeb.Endpoint with cowboy 2.7.0 at :::4000 (http)
[info] Access PersorgWeb.Endpoint at http://localhost
request_id=Fftn2pMOl7uhd48AAAAE [info] GET /
request_id=Fftn2pMOl7uhd48AAAAE [info] Sent 200 in 6ms
.............etc
3 Likes