Adding a new app
Unfortunately, we are no longer accepting pull requests from external collaborators due to the high volume of PRs we have to deal with daily.
We have reached a point in which it has become too complicated to validate, test and maintain this huge amount of apps resulting in a poor experience for our users.
We are working at the moment to implement a new feature to allow you to add multiple app stores to your Runtipi instance, so you can add your own apps without the need to send a PR to our repository.
This feature will be available very soon, so stay tuned!
You can still follow this guide and add your custom apps to your own instance through the appsRepoUrl
property in the settings.json
file.
In this guide, we will show you how to add a new app to the Runtipi App Store. The process is pretty simple and should take you less than 20 minutes.
Prerequisites
- The app you want to add is free and open-source (or at least source available with a permissive license for personal use)
- The app you want to add has an official and maintained docker image
Let's add the app
Fork the repository
In order to open a pull request you need to fork the repository. Visit the official App Store repository (opens in a new tab) and start by clicking the "Fork" button in the upper right corner of the page. This will create a new repository with your name and an identical structure to the original repository.
Clone the repository locally
On your computer clone the repository you just forked.
git clone https://<your-github-username>/runtipi/runtipi-appstore
Create a new branch for your app
Navigate to the repoisitory you just cloned.
cd runtipi-appstore
Create a new branch for your app.
git checkout -b app/<app-name>
Create the app files
Each app requires at least these files:
- A
docker-compose.yml
file to run your app - A
config.json
file to configure your app - A description in markdown format
- A logo in jpg format
512x512
pixels)
Inside the repository open the apps
folder and create a new folder for your app. The name should be the same as the app name without spaces or capital letters.
The config.json
file
Create a new config.json
file inside the newly created folder:
{
"name": "My super app",
"id": "my-super-app",
"available": true,
"short_desc": "Its an awesome app!",
"author": "me",
"port": 8685,
"categories": ["development", "ai"],
"description": "This app does x thing and y thing.",
"tipi_version": 1,
"version": "v1.5.7",
"source": "https://github.com/me/my-awesome-app",
"exposable": true,
"supported_architectures": ["arm64", "amd64"],
"created_at": 1724134338800,
"updated_at": 1724134338800
}
This config.json
file contains all the minimum required options. Check out the reference
table for all available options.
Available categories : utilities
, network
, media
, development
, automation
, social
, utilities
, photography
, security
, featured
, books
, data
, music
, finance
, ai
. If you want to add a new category, please open a new issue.
When choosing a port, please make sure that it is not in use already. You can check by using the search feature in your editor (for example the search icon on Visual Studio Code). You can also visit the App Store repository (opens in a new tab) and look the README.md file for the list of used ports for each app.
Sometimes an app is requiring more info to run it such as passwords or username. You can leverage the form_fields
property in the config.json
file to ask such information. Let's take for example Nextcloud. The image requires a username and password. We can simply add two fields in the config.json that will be presented to the user before installing.
{
"form_fields": [
{
"type": "text",
"label": "Username",
"max": 50,
"min": 3,
"required": true,
"env_variable": "NEXTCLOUD_ADMIN_USER"
},
{
"type": "password",
"label": "Password",
"max": 50,
"min": 3,
"required": true,
"env_variable": "NEXTCLOUD_ADMIN_PASSWORD"
}
]
}
You can choose between different types of fields. The app will automatically validate the user input before submitting.
Type | Description | Example value |
---|---|---|
text | Just a string of text | username |
password | Will be hidden on typing | password |
An email address | test@example.org | |
number | Any number | 123 |
fqdn | Fully qualified domain name | example.org |
ip | Any valid ipv4 address | 192.168.2.100 |
fqdnip | Combination between ip and fqdn | 192.168.2.100 or example.org |
random | Generate a random value for the user | 2m3ffc0923rk93df9023f9 |
boolean | A checkbox | true or false |
Here is also a table of what a form_field
object requires:
Name | Description | Example value | Required |
---|---|---|---|
type | The type of the form field to use, see above. | text | yes |
label | The label to show the user. | Nextcloud Username | yes |
hint | A small hint to show the user. | The username you want to use for nextcloud | false |
placeholder | A placeholder for the form field | user | false |
default | Add a default value, used only if the required option is set to false | user | false |
regex | Use a reggex pattern to verify the user input | ^[0-9]+.[0-9]+.[0-9]+$ | false |
pattern_error | The error to show when the regex pattern validation fails | Invalid username | false |
min | The minium length for a text or password input | 5 | false |
max | The maximum length for a text or password input | 100 | false |
required | Wheter the form field is required or not | true | yes |
env_variable | The name of the variable you'll use in your docker-compose.yml file. | NEXTCLOUD_USERNAME | yes |
options | Options for multiple select | See bellow | false |
encoding | Used only in addintion to random type. Specify the random value encoding. Can be base64 or hex | base64 | false |
When using the field type random for a password or secret, the min value will be used to determine the length of the string. If the min value is omitted the default length is 32 characters.
Here is how to use the form fields in your docker compose file:
services:
nextcloud:
container_name: nextcloud
image: nextcloud:23.0.3-apache
restart: unless-stopped
ports:
- ${APP_PORT}:80
volumes:
- ${APP_DATA_DIR}/data/nextcloud:/var/www/html
environment:
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_ADMIN_USER
and NEXTCLOUD_ADMIN_PASSWORD
are coming from the user inputs example above.
Now let's take a look at the options
option in the form fields, this will be rendered as dropdown in the UI. You can use them like this:
{
"label": "Select your favorite fruits",
"type": "text",
"required": true,
"options": [
{ "label": "Apple", "value": "apple" },
{ "label": "Banana", "value": "banana" },
{ "label": "Orange", "value": "orange" }
],
"env_variable": "fruits"
}
The docker-compose.yml
file
In the same folder, create a docker-compose.yml
file with your app config.
version: "3.9"
services:
my-app: # Should be exact same name as "id" field in config.json
container_name: my-app # Should be exact same name as "id" field in config.json
image: my-app:1.0.0 # Try to avoid "latest" tag. As it may break configs in the future.
restart: unless-stopped # Do not change this
environment:
- TZ=${TZ} # Can use any env variable. See "environment variables" section in the docs
volumes:
- ${APP_DATA_DIR}/data/config:/config # Always start the path with ${APP_DATA_DIR}/data/. This will put all data inside app-data/my-app/data
- ${APP_DATA_DIR}/data/projects:/projects
ports:
- ${APP_PORT}:8443
restart: unless-stopped
networks:
- tipi_main_network # That should not be changed
labels: # Use your editors search and replace feature to replace all instances of "myapp" with your app name in the traefik labels
# Main
traefik.enable: true
traefik.http.middlewares.myapp-web-redirect.redirectscheme.scheme: https
traefik.http.services.myapp.loadbalancer.server.port: 8443 # Should be the same as the app internal port so for this example 9443
# Web
traefik.http.routers.myapp-insecure.rule: Host(`${APP_DOMAIN}`)
traefik.http.routers.myapp-insecure.entrypoints: web
traefik.http.routers.myapp-insecure.service: myapp
traefik.http.routers.myapp-insecure.middlewares: myapp-web-redirect
# Websecure
traefik.http.routers.myapp.rule: Host(`${APP_DOMAIN}`)
traefik.http.routers.myapp.entrypoints: websecure
traefik.http.routers.myapp.service: myapp
traefik.http.routers.myapp.tls.certresolver: myresolver
# Local domain
traefik.http.routers.myapp-local-insecure.rule: Host(`myapp.${LOCAL_DOMAIN}`)
traefik.http.routers.myapp-local-insecure.entrypoints: web
traefik.http.routers.myapp-local-insecure.service: myapp
traefik.http.routers.myapp-local-insecure.middlewares: myapp-web-redirect
# Local domain secure
traefik.http.routers.myapp-local.rule: Host(`myapp.${LOCAL_DOMAIN}`)
traefik.http.routers.myapp-local.entrypoints: websecure
traefik.http.routers.myapp-local.service: myapp
traefik.http.routers.myapp-local.tls: true
If your app requires different docker-compose files for different architectures, you can use the following naming convention:
docker-compose.arm64.yml
If the user is running on an arm64 architecture, this file will be used instead of the default docker-compose.yml
.
The docker-compose.json
file (optional)
If you would like your app to have all our advanced features (like the ability to use only a reverse proxy or disable the app's port), you can check our Dynamic Compose (opens in a new tab) section to add the new compose file. In the future we are planning to mainstream this new dynamic compose. You would help us a lot by including this file since it will make the later migration faster.
The metadata/description.md
file
Here is located the long description of the app in markdown (html is supported too), usually a copy of the readme provided by the app's author. Make sure that when using images they are coming from github or an image service since tipi doesn't support loading images from local storage in the description.
The metadata/logo.jpg
file
That's a simple logo of the app in .jpg
format. In order for the app to look good make sure to use an image of 512x512
pixels and that it
of course looks good in the dashboard. Additionally if you can it would be nice to try to avoid the white-background-logo format since it makes the app look
bad in the appstore.
The data folder
If your app requires default files or configuration, you can easily provide those by creating a data
folder beside the app config.
- config.json
- docker-compose.yml
- anything.conf
- config.ini.template
- description.md
- logo.jpg
Anything placed under data will be copied over app-data/<app-id>/data
before installation. Then you can mount those files inside your compose file.
my-app:
container_name: my-app
restart: unless-stopped
volumes:
- ${APP_DATA_DIR}/data:/var/lib/config # Will mount the folder with `anything.conf` inside
If one of your files requires some dynamic values, you can use a template file. Let's take the example of a config.ini
file that requires the APP_PORT
variable.
[my-app]
port = {{APP_PORT}}
If you name your file config.ini.template
, it will automatically replace the variables with the values defined in the environment variables.
For example if you put the previous example in data/config.ini.template
, it will be copied over to app-data/<app-id>/data/config.ini
with the APP_PORT
variable replaced.
Commit your changes
Once you're done, you can commit your changes to the repository.
git add .
git commit -m "Add my-app"
git push origin master
Test your app in your own instance
Before submitting your app, you can test it in your own instance. You can do so by adding your app repository to the appsRepoUrl
property in the settings.json
file.
Visit the Using your own appstore repository section to learn more.
Submit a Pull Request
You can now submit a pull request to the repository.
- On GitHub, visit your repository and click on the "Pull requests" tab.
- Click on "New pull request" and fill in the title and description.
- Choose your branch and target the main repository at
runtipi/runtipi-appstore
.