# Install plugins on a self-hosted site

**URL:** https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157
**Category:** Self-Hosting
**Tags:** how-to
**Created:** [August 22, 2014, 2:59am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157 "2014-08-22T02:59:12Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [August 22, 2014, 2:59am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/1 "2014-08-22T02:59:12Z")

</div>

> :warning: This guide assumes that you have a **self-hosted standard installation**. We _only_ support the standard method of install here, so these instructions assume you have a [standard install](https://meta.discourse.org/t/142537?silent=true).

> :warning: This guide only applies to **self-hosted Discourse instances**. If you are using a managed hosting service, the available plugins are controlled by your hosting provider. For example, on our hosting [these specific plugins](https://www.discourse.org/plugins) are available by hosting tier.

> :information_source: As of mid-2025, [many popular official plugins have been bundled into Discourse core](https://meta.discourse.org/t/373574). These plugins (including Solved, [Data Explorer](https://meta.discourse.org/t/32566?silent=true), AI, Assign, Reactions, and many more) no longer need to be installed separately — they ship with Discourse and can be enabled via site settings. If you try to rebuild with a bundled plugin still in your `app.yml`, you’ll see a `HINT` message telling you to remove it. This guide is for plugins that are **not** bundled with core.

In this tutorial, we’ll install the [Discourse Shared Edits](https://meta.discourse.org/t/discourse-shared-edits/167583?tl=en) plugin.

This tutorial requires an understanding of how to use GitHub repos, specifically, [how to get or copy the git clone URL](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) and how to edit YAML (`*.yml`) files via the terminal using nano, specifically [how to save and exit on nano](https://linuxize.com/post/how-to-use-nano-text-editor/#saving-and-exiting). YAML files strongly follow indentations, so be sure to respect these as you copy and paste and edit the necessary `*.yml` for your Discourse instance. Always use spaces to indent in .yml files, not tabs.

- Copy the plugin’s GitHub `git clone` url.

- Access your container’s `app.yml` file (present in ` /var/discourse/containers/`)

- Add the plugin’s repository URL to your container’s `app.yml` file:

- Rebuild the container:

* * *

### If your plugin is hosted in a private repository

You must use an OAuth token:

> **[Managing your personal access tokens - GitHub Docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)**
>
> You can use a personal access token in place of a password when authenticating to GitHub in the command line or with the API.

With the OAuth token, you can install your plugin in the same way as a public repo, and you don’t need to create a SSH key.

```plaintext
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
              - git clone https://<token>@github.com/owner/repo.git

```

We strongly advise you to use OAuth tokens for plugins in private repositories. However, if you cannot, see below.

> **SSH Key Private Install Steps**
>
> - Assuming that your private git repo is hosted on GitHub, you need to add your server’s user public SSH key to GitHub repo Deploy keys ([https://github.com/SecretOrg/secret-plugin/settings/keys](https://github.com/SecretOrg/secret-plugin/settings/keys)).
> 
> - Modify `app.yml` file as shown in below example:
> 
> ```plaintext
> run:
> - exec: echo "Beginning of custom commands"
>   
> - exec: cd /var/www/discourse && sudo -u discourse bundle install --deployment --without test --without development
> - exec: echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /user/.ssh/config
> - file:
> path: /user/.ssh/id_rsa
> chmod: 600
> contents: |
> -----BEGIN RSA PRIVATE KEY-----
> MIIEogIBAAKCAQEArCQG213utzqE5YVjTVF5exGRCkE9OuM7LCp/FOuPdoHrFUXk
> .... etc ....
> -----END RSA PRIVATE KEY-----
> - file:
> path: /user/.ssh/id_rsa.pub
> chmod: 600
> contents: ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tj .... etc .... user@discourse
> - exec: cd $home/plugins && git clone git@github.com:SecretOrg/secret-plugin.git
> - exec: cd $home && sudo -E -u discourse bundle exec rake db:migrate
> - exec: cd $home && rm -fr tmp/cache
> - exec: cd $home && sudo -E -u discourse bundle exec rake assets:precompile
> - exec: rm /user/.ssh/id_rsa
> - exec: rm /user/.ssh/id_rsa.pub
> 
> - exec: echo "End of custom commands"
> 
> ```
> 
> - Rebuild the container:
> 
> ```plaintext
> cd /var/discourse
> ./launcher rebuild app
> 
> ```
> 
> Your private plugin should be installed on your Discourse instance.

### How to uninstall a plugin

To remove a plugin, simply remove the `- git clone https://github.com/...` line from your `app.yml` file and rebuild your site via

```plaintext
cd /var/discourse
./launcher rebuild app

```

> Last edited by @sam 2026-03-18T03:38:49Z
> 
> > **Check document**
> >
> > Perform check on document:

---

<div class="post-metadata">

### Author: ![wal](https://avatars.discourse-cdn.com/v4/letter/w/d6d6ee/32.png) [@wal](https://meta.discourse.org/u/wal)
#### Post date: [August 13, 2024, 4:21am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/339 "2024-08-13T04:21:53Z")

</div>

sorry for the dumb question but, how should I know if its required to include `sudo -E -u discourse` or not for the `git clone` statement?

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [August 18, 2024, 5:59am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/340 "2024-08-18T05:59:53Z")

</div>

How does your `docker_manager` line look like?

> [@Discourse](#):
>
> - Follow the existing format of the `docker_manager.git` line; if it does not contain `sudo -E -u discourse` then insert `- git clone https://github.com/discourse/discourse-solved.git`.

---

<div class="post-metadata">

### Author: ![wal](https://avatars.discourse-cdn.com/v4/letter/w/d6d6ee/32.png) [@wal](https://meta.discourse.org/u/wal)
#### Post date: [August 19, 2024, 12:40pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/341 "2024-08-19T12:40:47Z")

</div>

none of the other entries in my app.yml had the `sudo` part so I omitted that and its just worked so far

---

<div class="post-metadata">

### Author: ![Overgrow](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/overgrow/32/478189_2.png) [@Overgrow](https://meta.discourse.org/u/Overgrow)
#### Post date: [August 27, 2024, 9:25pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/342 "2024-08-27T21:25:19Z")

</div>

> [@Discourse](#):
>
> Modify `app.yml` file as shown in below example:

```plaintext
- exec: echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /user/.ssh/config

```

Seems like this method is not working anymore.

```plaintext
sh: 1: cannot create /user/.ssh/config: Directory nonexistent

```

With my self-hosted GIT repository, I’d really like to use SSH keys.

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [August 27, 2024, 10:20pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/343 "2024-08-27T22:20:29Z")

</div>

You shouldn’t need an ssh key in the container, just delete this:

> [@Overgrow](#):
>
> ```plaintext
> - exec: echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /user/.ssh/config
> 
> ```

---

<div class="post-metadata">

### Author: ![Overgrow](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/overgrow/32/478189_2.png) [@Overgrow](https://meta.discourse.org/u/Overgrow)
#### Post date: [August 27, 2024, 10:22pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/344 "2024-08-27T22:22:28Z")

</div>

Sorry, I don’t think I follow..

My repo is accessible via ssh. How can I get a plugin cloned without SSH key?

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [August 27, 2024, 10:35pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/345 "2024-08-27T22:35:17Z")

</div>

Oh. Maybe I’m wrong. Then maybe you need to create that directory with the right permissions m

---

<div class="post-metadata">

### Author: ![Overgrow](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/overgrow/32/478189_2.png) [@Overgrow](https://meta.discourse.org/u/Overgrow)
#### Post date: [August 29, 2024, 9:22am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/346 "2024-08-29T09:22:01Z")

</div>

What is confusing is that part of the commands in examples are to be run as `root` and part as user `discourse`. It doesn’t seem to be very consisent.

```plaintext
  - exec: cd $home/plugins && git clone git@github.com:SecretOrg/secret-plugin.git

```

As opposed to

```plaintext
  - exec: cd $home && sudo -E -u discourse bundle exec rake db:migrate
 

```

I will do some digging in the running container’s shell.

---

<div class="post-metadata">

### Author: ![nwnuyhs](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/nwnuyhs/32/444904_2.png) [@nwnuyhs](https://meta.discourse.org/u/nwnuyhs)
#### Post date: [September 2, 2024, 7:40am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/347 "2024-09-02T07:40:20Z")

</div>

This plugin installation is a bit complicated. It would be great if it could be made the same as installing theme.

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [September 2, 2024, 10:34am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/348 "2024-09-02T10:34:09Z")

</div>

[Dashboard.literatecomputing.com](http://Dashboard.literatecomputing.com) will install plugins with a few clicks. You can use it for free by joining the free trial group.

---

<div class="post-metadata">

### Author: ![Overgrow](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/overgrow/32/478189_2.png) [@Overgrow](https://meta.discourse.org/u/Overgrow)
#### Post date: [September 2, 2024, 4:38pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/349 "2024-09-02T16:38:48Z")

</div>

> [@Discourse](#):
>
> SSH Key Private Install Steps

@Discourse, If you want to update the guide, here is the working code:

To use your own SSH keys for accessing a private repository, follow these steps:

1. Place the private SSH key in `/var/discourse/shared/standalone/identity/id_rsa` (or another location accessible by the container; if you choose a different location, remember to update the path in the template below accordingly).

2. Use the following template to add your plugins:

```plaintext
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          # Set private repo access
          - chown $(whoami) /shared/identity/id_rsa && chmod 600 /shared/identity/id_rsa
          - git config --global core.sshCommand "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i /shared/identity/id_rsa"

          # My private plugins
          - git clone git@example.com:repo/private-discourse-plugin.git

          # Unset access
          - git config --global --unset core.sshCommand

```

---

<div class="post-metadata">

### Author: ![Jagster](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/jagster/32/192154_2.png) [@Jagster](https://meta.discourse.org/u/Jagster)
#### Post date: [September 2, 2024, 5:36pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/351 "2024-09-02T17:36:26Z")

</div>

Thanks for something I don’t need that one but I live with simple original.

Do you want to explain why we should use your example? Is it because of this:

> use your own SSH keys for accessing a private repository

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [September 2, 2024, 5:45pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/352 "2024-09-02T17:45:04Z")

</div>

Why not just use a token in the URL?

---

<div class="post-metadata">

### Author: ![Jagster](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/jagster/32/192154_2.png) [@Jagster](https://meta.discourse.org/u/Jagster)
#### Post date: [September 2, 2024, 6:46pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/353 "2024-09-02T18:46:56Z")

</div>

> [@Jagster](#):
>
> Do you want to explain why we should use your example?

Well, we have tools to solve out this riddle :winking_face_with_tongue:

> **Understanding the Usefulness of Container Configuration Snippets
AI**
>
> **Jagster:**
> 
> Why someone would like to use that snippet in `container/app.ym`
> 
> **gpt-4o:**
> 
> To effectively answer your question, I need to understand the context around the specific snippet in `container/app.yml`. Could you please provide more details or the actual snippet you’re referring to? This way, I can give you a precise and helpful explanation.
> 
> **Jagster:**
> 
> Sorry, but I needed this composer.
> 
> This is the snippet:
> 
> ”””
> 
> ```plaintext
> hooks:
> after_code:
> - exec:
> cd: $home/plugins
> cmd:
> # Set private repo access
> - chown $(whoami) /shared/identity/id_rsa && chmod 600 /shared/identity/id_rsa
> - git config --global core.sshCommand "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i /shared/identity/id_rsa"
> 
> # My private plugins
> - git clone git@example.com:repo/private-discourse-plugin.git
> 
> # Unset access
> - git config --global --unset core.sshCommand
> 
> ```
> 
> ”””
> 
> **gpt-4o:**
> 
> Thank you for providing the snippet. This YAML snippet is intended to be included in the `container/app.yml` configuration file for a Discourse instance. Let’s break down what it does:
> 
> ```yaml
> hooks:
> after_code:
> - exec:
> cd: $home/plugins
> cmd:
> # Set private repo access
> - chown $(whoami) /shared/identity/id_rsa && chmod 600 /shared/identity/id_rsa
> - git config --global core.sshCommand "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i /shared/identity/id_rsa"
> 
> # My private plugins
> - git clone git@example.com:repo/private-discourse-plugin.git
> 
> # Unset access
> - git config --global --unset core.sshCommand
> 
> ```
> 
> ### Explanation
> 
> 1. **Setting Directory** :
> 
> 2. **Setting Private Repository Access** :
> 
> 3. **Cloning Private Repository** :
> 
> 4. **Unsetting SSH Command** :
> 
> ### When and Why to Use This Snippet
> 
> 1. **Private Repositories** : If you have a private Discourse plugin repository that is not publicly accessible and requires SSH key-based authentication.
> 
> 2. **Automation** : This script helps automate the process of setting up access to the private repository, cloning it, and ensuring the environment is reset afterwards. This is useful for continuous deployment setups or automated server provisioning.
> 
> 3. **Security Management** : It handles the SSH keys securely (setting file permissions and then unsetting the custom SSH command), critical for maintaining security posture during automated deployments.
> 
> ### Important Considerations
> 
> - Ensure your private SSH key (`id_rsa`) is securely managed and not exposed.
> - Verify the ownership and permissions are correctly set for your security policies.
> 
> For more detailed information and contexts about `app.yml` and hooks in Discourse, you can check relevant [Discourse documentation](https://github.com/discourse/discourse/blob/main/docs/INSTALL-cloud.md).
> 
> If you have any further questions or need additional details on the configuration, feel free to ask!

---

<div class="post-metadata">

### Author: ![rrodrigueznt](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/rrodrigueznt/32/351504_2.png) [@rrodrigueznt](https://meta.discourse.org/u/rrodrigueznt)
#### Post date: [October 15, 2024, 6:28pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/355 "2024-10-15T18:28:35Z")

</div>

> [@pfaffman](#):
>
> will install plugins with a few clicks

Hi! It sounds great! But I don’t know how I must proceed after joining the free trial group! Please, @pfaffman, could you help me? I’m looking to install SAML plugin. It would be great to test [Literate Computing](https://dashboard.literatecomputing.com) to get it installed and running!

Thanks!

---

<div class="post-metadata">

### Author: ![Overgrow](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/overgrow/32/478189_2.png) [@Overgrow](https://meta.discourse.org/u/Overgrow)
#### Post date: [October 15, 2024, 9:43pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/356 "2024-10-15T21:43:57Z")

</div>

> [@merefield](#):
>
> Why not just use a token in the URL?

My template snippet is for when you don’t have any other means of accessing the Git repo besides an SSH key (for example, with a self-hosted Git repository behind SSH).

---

<div class="post-metadata">

### Author: ![TedJohnson](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/tedjohnson/32/486408_2.png) [@TedJohnson](https://meta.discourse.org/u/TedJohnson)
#### Post date: [February 11, 2025, 5:15pm UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/358 "2025-02-11T17:15:49Z")

</div>

> [@Discourse](#):
>
> Add the plugin’s repository URL to your container’s `app.yml` file:
> 
> ```plaintext
> hooks:
> after_code:
> - exec:
> cd: $home/plugins
> cmd:
> - sudo -E -u discourse git clone https://github.com/discourse/docker_manager.git
> - sudo -E -u discourse git clone https://github.com/discourse/discourse-solved.git
> 
> ```

What would have helped me was if these instructions said something like, “Hey dummy! Don’t run the `git clone` command from the shell. Just add the command to the `app.yml` file per the example, make sure the indents are correct, save it, and then run the rebuild command.”

These instructions could also be clearer when `-E -u` are necessary and when they are not.

I did some other dumb things to before I succeeded. When I look at the instructions now, they make nearly perfect sense. But I fell into several pitfalls that I might have avoided if these instructions were written considering noobs like me would be trying to follow them.

---

<div class="post-metadata">

### Author: ![alltiagocom](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/alltiagocom/32/492709_2.png) [@alltiagocom](https://meta.discourse.org/u/alltiagocom)
#### Post date: [March 15, 2025, 9:29am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/359 "2025-03-15T09:29:17Z")

</div>

I’m no expert, so bear with me…  
Yesterday I installed Discourse and now I’m trying to install a plugin.  
Do I need to run:  
`./launcher rebuild app`

or can I just run:  
`./launcher destroy app`  
and then  
`./launcher start app`  
?

Running `./launcher rebuild app` takes a long time and my website stops working or it becomes super slow.

---

<div class="post-metadata">

### Author: ![Jagster](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/jagster/32/192154_2.png) [@Jagster](https://meta.discourse.org/u/Jagster)
#### Post date: [March 15, 2025, 9:42am UTC](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157/360 "2025-03-15T09:42:47Z")

</div>

Rebuilding is the only way to install plugins. Destroying and starting use only the old configuration.

But don’t destroy anything before rebuilding. Because if rebuild fails you can put old one online using start.

You have one container setup, right? Then that forum will be offline, because first rebuilding will stop your container. And yes, it takes long time to rebuild.

There is two container setup two, and then offline time is few minutes. But don’t change to it before you are comfortable with these basic operations.

[Next page](https://meta.discourse.org/t/install-plugins-on-a-self-hosted-site/19157.md?page=2)
