# Deploy Discourse without Docker

**URL:** https://meta.discourse.org/t/deploy-discourse-without-docker/351194
**Category:** Sysadmins
**Tags:** unsupported-install, advanced-setup
**Created:** [February 8, 2025, 3:56am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194 "2025-02-08T03:56:12Z")
**Posts on this page:** 14
**Page:** 2

<div class="post-metadata">

### Author: ![lion](https://avatars.discourse-cdn.com/v4/letter/l/57b2e6/32.png) [@lion](https://meta.discourse.org/u/lion)
#### Post date: [September 22, 2025, 7:56pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/22 "2025-09-22T19:56:28Z")

</div>

Some details about my solutions:

1. Nginx config:  
Behind a reverse proxy (haproxy), you need to copy the `/var/discourse/config/nginx-config-sample.conf` to `/etc/nginx/sites-enabled/discourse.conf` or similar target, then replace the `listen <yourreverseproxyport>` (if needed), and the `_` of `server_name` by your hostname ([subdmain.example.com](http://subdmain.example.com)). Nothing else is to be changed.
2. Pages not displayed (“Oops” message) and email admin registration not working:  
Magick was the cause of not displaying pages after admin registration pages at first boot. I followed the file indicated in the log: `/var/www/discourse/lib/letter_avatar.rb:112` at line 112, there were two `magick` commands that indeed did not responded. convert responded, so i replaced `magick` at this line with `convert`. After these corrections, another error was logged. Trying the same procedure with the given command from file, `magick` command did not work, and convert was failing. NB: `magick --version` was 7.x and `convert --version` gave 6.x. The cause was that i installed first `imagemagick` with apt, then magick 7 from source. There were some conflicts and magick told convert command to be out of date. So I relaunched my script with magick 7 only. This solved immediately the problem and I could see the brand new pages awaited for days, and email worked too! Magick is really magic.
3. It still remains the problem of mixed content, but the website although works fine.  
NB: in puma.rb, the line `bind ENV.fetch("PUMA_BIND", "tcp://#{ENV['PUMA_BIND_ALL'] ? '' : '127.0.0.1:'}3000")` must be like this (correction of first post.)
4. EDIT “3.”, about https force for discourse: I don’t know why exactly, but my browsers don’t show ‘mixed content’ warnings any more (maybe because of the cache update), so it is all good now. I just need to finish my script.

---

<div class="post-metadata">

### Author: ![dodger](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/dodger/32/522280_2.png) [@dodger](https://meta.discourse.org/u/dodger)
#### Post date: [September 23, 2025, 10:51am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/23 "2025-09-23T10:51:32Z")

</div>

Confirmed working!!!  
ImageMagick v7 solves Ooops problem!  
As long as I’ve tested, its fully working.  
I’ll test the rest of features and report asap.

---

<div class="post-metadata">

### Author: ![dodger](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/dodger/32/522280_2.png) [@dodger](https://meta.discourse.org/u/dodger)
#### Post date: [September 24, 2025, 7:59am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/24 "2025-09-24T07:59:10Z")

</div>

> [@lion](#):
>
> NB: in puma.rb, the line `bind ENV.fetch("PUMA_BIND", "tcp://#{ENV['PUMA_BIND_ALL'] ? '' : '127.0.0.1:'}3000")` must be like this (correction of first post.)

What I did for testing was set the environment variable `PUMA_BIND` when running puma.

Regarding ImageMagick, for some reason Discourse is failing to execute an image conversion on the webUI, when I upload a big image it kindly refuse to conver it… I’m still debugging the problem.

---

<div class="post-metadata">

### Author: ![lion](https://avatars.discourse-cdn.com/v4/letter/l/57b2e6/32.png) [@lion](https://meta.discourse.org/u/lion)
#### Post date: [September 27, 2025, 12:10pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/25 "2025-09-27T12:10:24Z")

</div>

Any advance in debug? On my side, I am working on magick.  
On some around 50ko image, in browser a pop-up displays:  
`timeout -k 40.0 20 magick gif:/tmp/RackMultipart20250927-23598-xrrp6e.gif -auto-orient -background white -interlace none -flatten -debug all -quality 90 jpg:/tmp/image20250927-23598-9ujq3d.jpg` and no image is loaded  
If bigger size, no pop-up but loading wheel is turning in loop infinitely with no result. No error is logged in /var/www/discourse/log.

---

<div class="post-metadata">

### Author: ![dodger](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/dodger/32/522280_2.png) [@dodger](https://meta.discourse.org/u/dodger)
#### Post date: [September 27, 2025, 2:30pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/26 "2025-09-27T14:30:05Z")

</div>

Same problem for me 😂😭

The only way i was able to get it running was using imagemagick from brew repo.

BUT it fails when the image was more than 3MB… maybe i setup a very restrictive policy config.

Give it a try!!!

I’m testing the docker install but I think or pretty stupid deploy nginx, unicorn, redis, postgresql and the rest in ONLY one container… it doesn’t make sense at all. And there’s no infrastructure documentation for big deployments… I’ve work on IT for more than 20 years and I only see problem coming in the near future using such infra.

Without talking about “docker The Space Eater” ( like Dormammu) 🤣🤣

---

<div class="post-metadata">

### Author: ![lion](https://avatars.discourse-cdn.com/v4/letter/l/57b2e6/32.png) [@lion](https://meta.discourse.org/u/lion)
#### Post date: [September 27, 2025, 7:48pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/27 "2025-09-27T19:48:15Z")

</div>

> [@Deploy Discourse without Docker](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/25):
>
> Any advance in debug? On my side, I am working on magick. On some around 50ko image, in browser a pop-up displays: timeout -k 40.0 20 magick gif:/tmp/RackMultipart20250927-23598-xrrp6e.gif -auto-orient -background white -interlace none -flatten -debug all -quality 90 jpg:/tmp/image20250927-23598-9ujq3d.jpg and no image is loaded If bigger size, no pop-up but loading wheel is turning in loop infinitely with no result. No error is logged in /var/www/discourse/log.

This issue is gone with the following solution described at the end:

Debug 1: `bundle db:create` outputs, also in logs at first boot:

> …  
> unknown OID 21096: failed to recognize type of ‘embeddings’. It will be treated as String.  
> pngquant worker: `pngquant` not found; please provide proper binary or disable this worker (–no-pngquant argument or `:pngquant => false` through options)  
> oxipng worker: `oxipng` not found; please provide proper binary or disable this worker (–no-oxipng argument or `:oxipng => false` through options)  
> jhead worker: `jhead` not found, `jpegtran` not found; please provide proper binary or disable this worker (–no-jhead argument or `:jhead => false` through options)  
> jpegoptim worker: `jpegoptim` not found; please provide proper binary or disable this worker (–no-jpegoptim argument or `:jpegoptim => false` through options)  
> …

Debug 2: Some magick command with files output:

> no decode delegate for this image format

This last Issue is mentioned [here](https://forum.howtoforge.com/threads/solved-magick-no-decode-delegate-for-this-image-format-jpg.89735/).

Solution is [here](https://github.com/SoftCreatR/imei/?tab=readme-ov-file) (installs magick with formats plugins):

```plaintext
t=$(mktemp) && \
wget 'https://dist.1-2.dev/imei.sh' -qO "$t" && \
bash "$t" && \
rm "$t"

```

After this, my upload size can be up to 518KB. Above not. This is only for images. All others documents, audio, video uploads work.

Temporary solution for the resting issue:  
I’ve looked in the admin settings, discourse.conf, nginx/sites-enables/discourse.conf, in the git folder. Finally in AdminPanel/Parameters/Files I’ve disabled “Composer media optimization image enabled”, then everything is working fine. I can upload any image.

---

<div class="post-metadata">

### Author: ![dodger](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/dodger/32/522280_2.png) [@dodger](https://meta.discourse.org/u/dodger)
#### Post date: [September 28, 2025, 9:43am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/28 "2025-09-28T09:43:29Z")

</div>

Yup, IMEI is almost the same solution a using brew but I’m sure it won’t take around 2GB of disk 🤣🤣😭😭😭🤬🤬🤬

I’ll check your solution for max filesize on uploads.

I’m also struggling with a solution for sending emails “for free” (I’m on an early stage and don’t wanna contract mailtrap/mailgun/etc…)

---

<div class="post-metadata">

### Author: ![lion](https://avatars.discourse-cdn.com/v4/letter/l/57b2e6/32.png) [@lion](https://meta.discourse.org/u/lion)
#### Post date: [September 28, 2025, 6:42pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/29 "2025-09-28T18:42:40Z")

</div>

Why don’t you install a mail server ? I have my own and I scripted the install. Tell me if you are interested. You just need to pay yearly for a domain name.

---

<div class="post-metadata">

### Author: ![dodger](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/dodger/32/522280_2.png) [@dodger](https://meta.discourse.org/u/dodger)
#### Post date: [September 29, 2025, 6:11am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/30 "2025-09-29T06:11:09Z")

</div>

Sure! DM me with the instructions or git repo !😄

I stop using self hosted mail servers many years ago…

---

<div class="post-metadata">

### Author: ![MoRanYue](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moranyue/32/523065_2.png) [@MoRanYue](https://meta.discourse.org/u/MoRanYue)
#### Post date: [September 30, 2025, 10:55am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/31 "2025-09-30T10:55:16Z")

</div>

When doing `rake assets:precompile`, it said `No such file or directory - brotli`, just install it with a package manager.

---

<div class="post-metadata">

### Author: ![MoRanYue](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moranyue/32/523065_2.png) [@MoRanYue](https://meta.discourse.org/u/MoRanYue)
#### Post date: [October 18, 2025, 5:50am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/32 "2025-10-18T05:50:30Z")

</div>

I found that it is throwing this error:

```plaintext
bundler: failed to load command: puma (/home/mry/.rbenv/versions/3.4.6/bin/puma)
/home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/lib/puma/cluster.rb:472:in 'Puma::Cluster#run': undefined method 'wait_readable' for nil (NoMethodError)

            if read.wait_readable([0, @next_check - Time.now].max)
                   ^^^^^^^^^^^^^^
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/lib/puma/launcher.rb:202:in 'Puma::Launcher#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/lib/puma/cli.rb:73:in 'Puma::CLI#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/bin/puma:10:in '<top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/bin/puma:25:in 'Kernel#load'
        from /home/mry/.rbenv/versions/3.4.6/bin/puma:25:in '<top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Kernel.load'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Bundler::CLI::Exec#kernel_load'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:23:in 'Bundler::CLI::Exec#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:452:in 'Bundler::CLI#exec'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor.rb:538:in 'Bundler::Thor.dispatch'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:29:in 'Bundler::CLI.start'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/exe/bundle:28:in 'block in <top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/friendly_errors.rb:117:in 'Bundler.with_friendly_errors'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/exe/bundle:20:in '<top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/bin/bundle:25:in 'Kernel#load'
        from /home/mry/.rbenv/versions/3.4.6/bin/bundle:25:in '<main>'

```

then Puma restarts itself. What should I do to prevent this?

Edited: This seems to be [a problem belonging to Puma](https://github.com/puma/puma/issues/3620) since Ruby 3.4.0. Solved by disabling YJIT.

---

<div class="post-metadata">

### Author: ![lion](https://avatars.discourse-cdn.com/v4/letter/l/57b2e6/32.png) [@lion](https://meta.discourse.org/u/lion)
#### Post date: [October 18, 2025, 11:02am UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/33 "2025-10-18T11:02:23Z")

</div>

Then use 3.3.7. This is my used version.

---

<div class="post-metadata">

### Author: ![MoRanYue](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moranyue/32/523065_2.png) [@MoRanYue](https://meta.discourse.org/u/MoRanYue)
#### Post date: [October 18, 2025, 3:06pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/34 "2025-10-18T15:06:23Z")

</div>

I am too lazy to redownload gems, just disabling YJIT with continuing using 3.4.6.

---

<div class="post-metadata">

### Author: ![lion](https://avatars.discourse-cdn.com/v4/letter/l/57b2e6/32.png) [@lion](https://meta.discourse.org/u/lion)
#### Post date: [January 8, 2026, 5:05pm UTC](https://meta.discourse.org/t/deploy-discourse-without-docker/351194/36 "2026-01-08T17:05:01Z")

</div>

Hi all,  
Here is my script for a full install in three shots, without docker, on a Debian 12 container (lxc for me).  
Use the if blocks to only run the parts you need.

1. Set your parameter and run it as root with the required arguments. Be careful: the script will exit at the exit point before “### MANUAL” part.
2. You have to pass these commands manually in the container terminal. I did not succeed to make it work in my lxc unprivileged container. (default user is root)
3. Then run the rest.  
I tested it many times and it works but there may be some buggy parts after i adapted it to publication.

> **install\_script**
>
> ```plaintext
> #!/bin/bash
> ########## SCRIPT TO INSTALL DISCOURSE ON DEBIAN 12 WITHOUT DOCKER ##################
> #- last tested in december 2025
> ############################## RUN IT AS ROOT, best in a lxc container!!!!!!!
> #- discussion source : https://meta.discourse.org/t/deploy-discourse-without-docker/351194
> ##############################################################################
> # MANUAL PARAMETERS
> certbot_contact_email=hello@domain.tld
> certbot_renew_port=9785
> nginx_discourse_port=8080
> DISCOURSE_DEVELOPER_EMAILS='emailaddress'
> DISCOURSE_SMTP_ADDRESS=smtp_server_address
> DISCOURSE_SMTP_PORT=465
> DISCOURSE_SMTP_USER_NAME=xxx
> DISCOURSE_SMTP_PASSWORD=xxx
> DISCOURSE_SMTP_DOMAIN=xxx
> DISCOURSE_NOTIFICATION_EMAIL=no-reply-xxxx
> DISCOURSE_MAXMIND_ACCOUNT_ID=50
> # END MANUAL PARAMETERS
> 
> shopt -s expand_aliases
> source /etc/bash.bashrc
> container=$1
> cont_haproxy=$2
> pool=$3
> lxc_image=$4
> hostname=$5
> db_password=$6
> maxmind_license_key=$7
> DISCOURSE_HOSTNAME=$hostname
> DISCOURSE_MAXMIND_LICENSE_KEY=$maxmind_license_key
> 
> ### INSTALL MAGICK 7 and its plugins
> # https://github.com/SoftCreatR/imei/?tab=readme-ov-file
> if [1 == 1]; then
> t=$(mktemp) && \
> wget 'https://dist.1-2.dev/imei.sh' -qO "$t" && \
> bash "$t" && \
> rm "$t"
> fi
> 
> ### INSTALL BASICS ###
> if [1 == 1]; then
> apt install -y apt-utils postgresql nginx libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static redis
> systemctl enable --now postgresql redis-server nginx
> useradd -m -s /bin/bash discourse
> usermod -aG sudo discourse
> passwd -d discourse
> # RVM (RUBY)
> apt install -y ruby-full # nécessaire
> su - discourse -c 'curl -sSL https://get.rvm.io | bash'
> su - discourse -c 'source /home/discourse/.bashrc'
> su - discourse -c 'source /home/discourse/.rvm/scripts/rvm' # nécessaire après rvm install
> su - discourse -c 'rvm install 3.3.7' # will install node 3.3.7 as of today
> source /home/discourse/.rvm/scripts/rvm
> # fi
> # configure POSTGRESQL
> apt update && apt upgrade -y
> apt autoremove -y
> 
> # YOU HAVE TO ACCEPT THE UPGRADE
> /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
> apt install -y postgresql-18-pgvector
> fi
> alias mydb="sudo -u postgres psql discourse -c "
> if [1 == 1]; then
> su - discourse -c 'sudo -u postgres createuser -s discourse'
> # fi
> su - discourse -c 'sudo -u postgres createdb discourse'
> su - discourse -c 'sudo -u postgres psql -c "ALTER USER discourse WITH ENCRYPTED PASSWORD '\''password'\'';"'
> alias mydb="sudo -Hiu discourse psql -c "
> su - discourse -c 'psql -c "GRANT CONNECT ON DATABASE discourse TO root;"'
> su - discourse -c 'psql -c "CREATE EXTENSION pg_trgm;CREATE EXTENSION hstore;"'
> su - discourse -c 'psql -c "ALTER DATABASE discourse OWNER TO discourse;"'
> su - discourse -c 'psql -c "CREATE EXTENSION unaccent;CREATE EXTENSION plpgsql;"'
> su - discourse -c 'psql -c "CREATE EXTENSION vector;"'
> echo "OK"
> 
> ### INSTALL DISCOURSE FROM GITHUB
> cd /var/www/
> git clone https://github.com/discourse/discourse
> git config --global --add safe.directory /var/www/discourse
> # exit
> ### NÉCESSAIRE POUR bundle, ruby, etc. pas pour discourse lui-même
> tee -a /home/discourse/.bashrc > /dev/null <<EOT
> export RAILS_ENV=production
> export UNICORN_SIDEKIQ_MAX_RSS=800
> export UNICORN_WORKERS=8
> export UNICORN_SIDEKIQS=1
> export PUMA_SIDEKIQ_MAX_RSS=800
> export PUMA_WORKERS=8
> export PUMA_SIDEKIQS=1
> export RUBY_GC_HEAP_GROWTH_MAX_SLOTS=40000
> export RUBY_GC_HEAP_INIT_SLOTS=400000
> export RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=1.5
> 
> export DISCOURSE_HOSTNAME=$hostname
> export DISCOURSE_DEVELOPER_EMAILS=$DISCOURSE_DEVELOPER_EMAILS
> export DISCOURSE_SMTP_ADDRESS=$DISCOURSE_SMTP_ADDRESS
> export DISCOURSE_SMTP_PORT=$DISCOURSE_SMTP_PORT
> export DISCOURSE_SMTP_USER_NAME=$DISCOURSE_SMTP_USER_NAME
> export DISCOURSE_SMTP_PASSWORD=$DISCOURSE_SMTP_PASSWORD
> export DISCOURSE_SMTP_DOMAIN=$DISCOURSE_SMTP_DOMAIN
> export DISCOURSE_NOTIFICATION_EMAIL=$DISCOURSE_NOTIFICATION_EMAIL
> export DISCOURSE_MAXMIND_ACCOUNT_ID=$DISCOURSE_MAXMIND_ACCOUNT_ID
> export DISCOURSE_MAXMIND_LICENSE_KEY=$maxmind_license_key
> 
> export DISCOURSE_ENABLE_CORS=true
> export DISCOURSE_MAX_REQS_PER_IP_MODE=none
> export DISCOURSE_MAX_REQS_PER_IP_PER_MINUTE=20000
> export DISCOURSE_MAX_REQS_PER_IP_PER_10_SECONDS=5000
> export DISCOURSE_MAX_ASSET_REQS_PER_IP_PER_10_SECONDS=20000
> export DISCOURSE_MAX_REQS_RATE_LIMIT_ON_PRIVATE=false
> export DISCOURSE_MAX_USER_API_REQS_PER_MINUTE=200
> export DISCOURSE_MAX_USER_API_REQS_PER_DAY=28800
> export DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE=600
> export DISCOURSE_MAX_DATA_EXPLORER_API_REQ_MODE=none
> EOT
> cd /var/www/discourse
> git stash
> git pull
> git checkout tests-passed 
> cd plugins
> for plugin in *
> do
> echo "ok"
> echo $plugin; cd ${plugin}; git pull; cd ..
> done
> # fi
> chown -R discourse:discourse /var/www/discourse/     
> mkdir -p /var/www/discourse/public
> chown -R discourse:www-data /var/www/discourse/public
> tee -a /var/www/discourse/config/discourse.conf > /dev/null <<EOT
> max_data_explorer_api_req_mode = 'none'
> max_user_api_reqs_per_day = '28800'
> hostname = '$hostname'
> redis_host = 'localhost'
> smtp_user_name = $$DISCOURSE_SMTP_USER_NAME
> db_password = '$db_password'
> smtp_address = '$DISCOURSE_SMTP_ADDRESS'
> db_socket = ''
> max_reqs_per_ip_per_10_seconds = '5000'
> max_asset_reqs_per_ip_per_10_seconds = '20000'
> max_reqs_rate_limit_on_private = 'false'
> developer_emails = '$DISCOURSE_DEVELOPER_EMAILS'
> max_user_api_reqs_per_minute = '200'
> maxmind_license_key = '$maxmind_license_key'
> smtp_port = '$DISCOURSE_SMTP_PORT'
> maxmind_account_id = '$DISCOURSE_MAXMIND_ACCOUNT_ID'
> smtp_password = '$DISCOURSE_SMTP_PASSWORD'
> max_reqs_per_ip_per_minute = '20000'
> notification_email = '$DISCOURSE_NOTIFICATION_EMAIL'
> db_host = 'localhost'
> enable_cors = 'true'
> db_port = ''
> max_reqs_per_ip_mode = 'none'
> smtp_domain = '$DISCOURSE_SMTP_DOMAIN'
> #max_admin_api_reqs_per_minute = '600'
> smtp_force_tls='true'
> EOT
> 
> cd /var/www/discourse && sed -i '/gem "rails_multisite"/i gem "rails"' Gemfile
> cd /var/www/discourse && sed -i '/gem "image_optim"/i gem "image_optim_pack"' Gemfile
> su - discourse -c 'cd /var/www/discourse && bundle install'
> # fi
> 
> ### PNPM (NODEJS)
> su - discourse -c 'curl -fsSL https://get.pnpm.io/install.sh' | su - discourse -c 'sh -'
> su - discourse -c 'source /home/discourse/.bashrc'
> source /home/discourse/.rvm/scripts/rvm
> su - discourse -c 'sed -i "s/~> 3.3/3.3.7/" /var/www/discourse/Gemfile'
> chown -R discourse:discourse /var/www/discourse/Gemfile Gemfile.lock
> apt-get -yqq install brotli # is it needed ????????????????
> # fi
> su - discourse -c 'cd /var/www/discourse && bin/rails db:create'
> # fi
> su - discourse -c 'source /home/discourse/.rvm/scripts/rvm'
> tee -a .bashrc > /dev/null <<EOT
> # pnpm
> export PNPM_HOME="/home/discourse/.local/share/pnpm"
> case ":\$PATH:" in
> *":\$PNPM_HOME:"*) ;;
> *) export PATH="\$PNPM_HOME:\$PATH" ;;
> esac
> # pnpm end
> EOT
> which pnpm
> source .bashrc
> pnpm env use --global lts # will install node 22 as of today
> cd /var/www/discourse && pnpm i
> chown -R discourse:discourse .
> chown -R discourse:www-data public
> exit
> 
> #################################
> ######### MANUAL ################
> export PATH="$PATH:/home/discourse/.rvm/bin"
> rvm install "ruby-3.3.7" # install in root
> cd /var/www/discourse
> bundle install
> bundle exec rake db:migrate
> bundle exec rake themes:update
> bundle exec rake assets:precompile
> ######### MANUAL #################
> ##################################
> 
> fi
> ### puma.rb
> #cd /var/www/discourse
> cp /var/www/discourse/config/puma.rb /var/www/discourse/config/puma.rb_original
> 
> tee /var/www/discourse/config/puma.rb > /dev/null <<EOT
> # frozen_string_literal: true
> 
> require "fileutils"
> 
> discourse_path = File.expand_path(File.expand_path(File.dirname( __FILE__ )) + "/../")
> 
> enable_logstash_logger = ENV["ENABLE_LOGSTASH_LOGGER"] == "1"
> puma_stderr_path = "#{discourse_path}/log/puma.stderr.log"
> puma_stdout_path = "#{discourse_path}/log/puma.stdout.log"
> 
> # Load logstash logger if enabled
> if enable_logstash_logger
> require_relative "../lib/discourse_logstash_logger"
> FileUtils.touch(puma_stderr_path) if !File.exist?(puma_stderr_path)
> # Note: You may need to adapt the logger initialization for Puma
> log_formatter = proc do |severity, time, progname, msg|
> event = {
> "@timestamp" => Time.now.utc,
> "message" => msg,
> "severity" => severity,
> "type" => "puma"
> }
> "#{event.to_json}\n"
> end
> else
> stdout_redirect puma_stdout_path, puma_stderr_path, true
> end
> 
> # Number of workers (processes)
> workers ENV.fetch("PUMA_WORKERS", 8).to_i
> 
> # Set the directory
> directory discourse_path
> 
> # Bind to the specified address and port
> bind ENV.fetch("PUMA_BIND", "tcp://#{ENV['PUMA_BIND_ALL'] ? '' : '127.0.0.1:'}3000")
> 
> # PID file location
> FileUtils.mkdir_p("#{discourse_path}/tmp/pids")
> pidfile ENV.fetch("PUMA_PID_PATH", "#{discourse_path}/tmp/pids/puma.pid")
> 
> # State file - used by pumactl
> state_path "#{discourse_path}/tmp/pids/puma.state"
> 
> # Environment-specific configuration
> if ENV["RAILS_ENV"] == "production"
> # Production timeout
> worker_timeout 30
> else
> # Development timeout
> worker_timeout ENV.fetch("PUMA_TIMEOUT", 60).to_i
> end
> 
> # Preload application
> preload_app!
> 
> # Handle worker boot and shutdown
> before_fork do
> Discourse.preload_rails!
> Discourse.before_fork
> 
> # Supervisor check
> supervisor_pid = ENV["PUMA_SUPERVISOR_PID"].to_i
> if supervisor_pid > 0
> Thread.new do
> loop do
> unless File.exist?("/proc/#{supervisor_pid}")
> puts "Kill self supervisor is gone"
> Process.kill "TERM", Process.pid
> end
> sleep 2
> end
> end
> end
> 
> # Sidekiq workers
> sidekiqs = ENV["PUMA_SIDEKIQS"].to_i
> if sidekiqs > 0
> puts "starting #{sidekiqs} supervised sidekiqs"
> 
> require "demon/sidekiq"
> Demon::Sidekiq.after_fork { DiscourseEvent.trigger(:sidekiq_fork_started) }
> Demon::Sidekiq.start(sidekiqs)
> 
> if Discourse.enable_sidekiq_logging?
> Signal.trap("USR1") do
> # Delay Sidekiq log reopening
> sleep 1
> Demon::Sidekiq.kill("USR2")
> end
> end
> end
> 
> # Email sync demon
> if ENV["DISCOURSE_ENABLE_EMAIL_SYNC_DEMON"] == "true"
> puts "starting up EmailSync demon"
> Demon::EmailSync.start(1)
> end
> 
> # Plugin demons
> DiscoursePluginRegistry.demon_processes.each do |demon_class|
> puts "starting #{demon_class.prefix} demon"
> demon_class.start(1)
> end
> 
> # Demon monitoring thread
> Thread.new do
> loop do
> begin
> sleep 60
> 
> if sidekiqs > 0
> Demon::Sidekiq.ensure_running
> Demon::Sidekiq.heartbeat_check
> Demon::Sidekiq.rss_memory_check
> end
> 
> if ENV["DISCOURSE_ENABLE_EMAIL_SYNC_DEMON"] == "true"
> Demon::EmailSync.ensure_running
> Demon::EmailSync.check_email_sync_heartbeat
> end
> 
> DiscoursePluginRegistry.demon_processes.each(&:ensure_running)
> rescue => e
> Rails.logger.warn("Error in demon processes heartbeat check: #{e}\n#{e.backtrace.join("\n")}")
> end
> end
> end
> 
> # Close Redis connection
> Discourse.redis.close
> end
> 
> on_worker_boot do
> DiscourseEvent.trigger(:web_fork_started)
> Discourse.after_fork
> end
> 
> # Worker timeout handling
> worker_timeout 30
> 
> # Low-level worker options
> threads 8, 32
> EOT
> 
> ### CERTBOT
> apt install -y certbot
> certbot certonly --non-interactive --agree-tos --renew-with-new-domains --standalone --email $certbot_contact_email --rsa-key-size 4096 --http-01-port $certbot_renew_port -d $hostname
> # fi
> 
> ### NGINX
> cp /var/www/discourse/config/nginx.sample.conf /etc/nginx/sites-enabled/discourse.conf
> sed -i "s/80/$nginx_discourse_port/" /etc/nginx/sites-enabled/discourse.conf
> sed -i "s/server_name _/server_name $hostname/" /etc/nginx/sites-enabled/discourse.conf
> mkdir -p /var/nginx/cache
> systemctl enable nginx
> systemctl restart nginx
> systemctl status nginx
> 
> ### SYSTEMD SERVICE AUTOSTART
> sudo tee /etc/systemd/system/discourse.service > /dev/null <<EOT
> [Unit]
> Description=Discourse with Puma Server
> After=network.target postgresql.service
> Requires=postgresql.service
> 
> [Service]
> Type=simple
> User=discourse
> Group=discourse
> WorkingDirectory=/var/www/discourse
> # requires running `rvm 3.3.7 --default` before this service is run
> ExecStart=/usr/bin/bash -lc '/home/discourse/.rvm/gems/ruby-3.3.7/bin/puma -C config/puma.rb'
> ExecReload=/usr/bin/bash -lc '/home/discourse/.rvm/gems/ruby-3.3.7/bin/pumactl restart'
> # il faut cette ligne dessous
> Environment=RAILS_ENV=production
> # Restart configuration
> Restart=always
> RestartSec=5s
> 
> # Basic security measures
> NoNewPrivileges=true
> ProtectSystem=full
> ProtectHome=read-only
> 
> [Install]
> WantedBy=multi-user.target
> EOT
> 
> systemctl enable discourse
> systemctl start discourse
> 
> # OR MANUAL START
> # puma -C config/puma.rb
> 
> ### SETTINGS
> echo "LAST MANUAL SETTINGS:"
> echo ""
> echo "GO in Admin>files and add wanted file extensions - vidéos, audio et documents
> echo "TO FORCE HTTPS: https://domain.com/admin/config/security"
> 
> ```

[Previous page](https://meta.discourse.org/t/deploy-discourse-without-docker/351194.md?page=1)
