thbar

thbar

I’ve got a number of apps (e.g. Phoenix, but also pure Plug + Cowboy) where 1/ hot-reload cannot be easily used and 2/ the app runs on a single server.

The restart period can be a bit slow (and some apps are behind nginx too), which leads to a period during which the requests are refused (bad gateway etc).

I’m looking at ways to reduce that downtime.

Did anyone managed to use systemd “socket activation” together with either Phoenix or even Cowboy/Ranch directly?

For some context, here is how you can achieve that with Go:

or even with Python:

It could quite improve the deploy workflow of single-server setups.

If you have any information, let me know!

Thanks!

– Thibaut

Showing Posts 1 to 10

idi527

idi527

:waving_hand:

It’s possible to pass fd option to :gen_tcp.listen: gen_tcp — OTP 29.0.2 (kernel 11.0.2). So something like

# not sure about this ...
config :app, App.Endpoint,
  http: [transport_options: %{socket_opts: [fd: SYSTEMD_FIRST_SOCKET_FD]}]

might work.

So working from bottom up,

  • :gen_tcp’s fd option would probably make it work
  • ranch accepts socket options via :socket_opts
  • plug uses :transport_options to configure that part of :ranch
  • phoenix uses :http option to configure that part of plug
thbar

thbar OP

This looks like a solid start - I will experiment, many thanks!

tristan

tristan

Rebar3 Core Team

Very interested in hearing if you get it working. I tried once briefly and failed to get it to work.

I tried with {fd, 3} as an option to gen_tcp:listen.

I’ll have to dig it back up to try again and see where it went wrong, I remember it was a hassle to get the systemd service and socket working properly to even test the Erlang code and that is likely the reason it didn’t work, and not an issue with Erlang.

I know @garazdawi said he was interested as well.

thbar

thbar OP

I will experiment the coming week, on a client setup for which I need to do maintenance (Ansible + Elixir). Will report back with my findings!

amarraja

amarraja

I can’t help with the socket activation stuff, however I have been using a docker solution for zero downtime deploys, and it has been working great for me.

In a nutshell, it uses docker swarm which will start a new instance of the app, wait for it to become ready, move traffic over, and finally remove the old one. It’s surprisingly simple and works great on single server setups.

I can write up more details if people are interested.

thbar

thbar OP

While I won’t be able to use that technique on the projects I’m managing at the moment, I’d say it’s interesting to share anyway (if that’s not too much work on your side!).

thbar

thbar OP

An update on this: so far I’m not able to start the app, but here are bits of feedback in case someone else also tackles this (I will get back to this later).

I’ve created a file under /etc/systemd/system/my_app.socket, with content:

[Unit]
Description=My App Socket
PartOf=my_app.service

[Socket]
# this is the Phoenix port
ListenStream=4000
BindIPv6Only=both

[Install]
WantedBy=sockets.target

The documentation for ListenStream etc is at Ubuntu Manpage: systemd.socket - Socket unit configuration.

After a restart with reload systemctl_daemon, I can see an active unit for the socket with sudo systemctl status my_app.socket.

I’ve also modified my app service file under /etc/systemd/system/my_app.service, with (extract, cannot share in full):

[Unit]
# added my_app.socket
After=syslog.target network.target postgresql.service my_app.socket
Requires=my_app.socket

[Service]
Type=simple
User={{ item.user }}
# SNIP

I’ve (trial and error) updated the prod.exs configuration used to build my releases (via Distillery) with:

http: [:inet6, port: port, transport_options: [socket_opts: [fd: 3]]],

Ultimately though, my logs show the following error:

[error] Failed to start Ranch listener MyAppWeb.Endpoint.HTTP in
:ranch_tcp:listen([{:cacerts, :...}, {:key, :...}, {:cert, :...}, {:fd, 3}, :inet6,
{:port, 4000}]) for reason :einval (invalid argument)

This is already progress, because I had a lot of other errors before earlier, but at this point I must dive deeper into Ranch to figure out what is happening.

tristan

tristan

Rebar3 Core Team

I’d suggest starting with just a gen_tcp module and not involve ranch or anything else.

idi527

idi527

Getting the same error {:error, :einval} with :gen_tcp.listen(5000, fd: 3) and the following systemd units:

[Unit]
Description=My App Socket
PartOf=my_app.service

[Socket]
ListenStream=5000
BindIPv6Only=both

[Install]
WantedBy=sockets.target
[Unit]
Description=My app daemon
After=syslog.target network.target my_app.socket
Requires=my_app.socket

[Service]
Type=simple
User=ubuntu
Group=ubuntu
Restart=on-failure
WorkingDirectory=/opt/socket_activation_demo
ExecStart=/opt/socket_activation_demo/bin/socket_activation_demo foreground

[Install]
WantedBy=multi-user.target

Just in case, here’s the env vars set for the service

iex(socket_activation_demo@127.0.0.1)15> System.get_env |> Enum.each(&IO.inspect/1)
{"HOME", "/home/ubuntu"}
{"TERM", "xterm"}
{"ERTS_VSN", "10.3.1"}
{"ERTS_DIR", "/opt/socket_activation_demo/erts-10.3.1"}
{"POST_CONFIGURE_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/post_configure.d"}
{"SRC_SYS_CONFIG_PATH", "/opt/socket_activation_demo/releases/0.1.0/sys.config"}
{"LANG", "C.UTF-8"}
{"SHLVL", "0"}
{"NAME_ARG", "-name socket_activation_demo@127.0.0.1"}
{"DISTILLERY_VSN", "2.1.1"}
{"PRE_UPGRADE_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/pre_upgrade.d"}
{"RELEASES_DIR", "/opt/socket_activation_demo/releases"}
{"PATH", "/opt/socket_activation_demo/erts-10.3.1/bin:/opt/socket_activation_demo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
{"CONSOLIDATED_DIR", "/opt/socket_activation_demo/lib/socket_activation_demo-0.1.0/consolidated"}
{"REL_DIR", "/opt/socket_activation_demo/releases/0.1.0"}
{"RELEASE_CONFIG_DIR", "/opt/socket_activation_demo"}
{"POST_UPGRADE_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/post_upgrade.d"}
{"REL_NAME", "socket_activation_demo"}
{"NAME", "socket_activation_demo@127.0.0.1"}
{"ROOTDIR", "/opt/socket_activation_demo"}
{"DEST_VMARGS_PATH", "/opt/socket_activation_demo/var/vm.args"}
{"RELEASE_ROOT_DIR", "/opt/socket_activation_demo"}
{"OLDPWD", "/opt"}
{"RELEASE_MUTABLE_DIR", "/opt/socket_activation_demo/var"}
{"PRE_CONFIGURE_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/pre_configure.d"}
{"ERL_OPTS", ""}
{"NAME_TYPE", "-name"}
{"DEST_SYS_CONFIG_PATH", "/opt/socket_activation_demo/var/sys.config"}
{"LISTEN_FDNAMES", "my_app.socket"}
{"SRC_VMARGS_PATH", "/opt/socket_activation_demo/releases/0.1.0/vm.args"}
{"ESCRIPT_NAME", "/opt/socket_activation_demo/releases/0.1.0/socket_activation_demo.sh"}
{"SHELL", "/bin/bash"}
{"LD_LIBRARY_PATH", "/opt/socket_activation_demo/erts-10.3.1/lib:"}
{"PRE_START_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/pre_start.d"}
{"BINDIR", "/opt/socket_activation_demo/erts-10.3.1/bin"}
{"DISTILLERY_TASK", "foreground"}
{"LISTEN_PID", "5248"}
{"POST_STOP_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/post_stop.d"}
{"PWD", "/opt/socket_activation_demo"}
{"ERTS_LIB_DIR", "/opt/socket_activation_demo/lib"}
{"SYS_CONFIG_PATH", "/opt/socket_activation_demo/var/sys.config"}
{"PROGNAME", "opt/socket_activation_demo/releases/0.1.0/socket_activation_demo.sh"}
{"LISTEN_FDS", "1"}
{"VMARGS_PATH", "/opt/socket_activation_demo/var/vm.args"}
{"PRE_STOP_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/pre_stop.d"}
{"REL_VSN", "0.1.0"}
{"EMU", "beam"}
{"POST_START_HOOKS", "/opt/socket_activation_demo/releases/0.1.0/hooks/post_start.d"}
{"LOGNAME", "ubuntu"}
{"USER", "ubuntu"}
{"START_ERL_DATA", "/opt/socket_activation_demo/var/start_erl.data"}
{"JOURNAL_STREAM", "9:98738"}
{"RUN_ERL_ENV", ""}
{"INVOCATION_ID", "3f625ea198164b3094464e146ff67c0b"}
:ok

the relevant ones are probably:

{"LISTEN_FDS", "1"}
{"LISTEN_FDNAMES", "my_app.socket"}

Can connect to it so it works …

iex(socket_activation_demo@127.0.0.1)12> :gen_tcp.connect({0,0,0,0}, 5000, [:binary])
{:ok, #Port<0.55>}

But cannot listen :frowning:

tristan

tristan

Rebar3 Core Team

Yea, I think that is what I got when I tried as well and never had the time to debug further.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews