Firstimer - a long way to complete my first app package
7 min read

Firstimer - a long way to complete my first app package

I am not a developer, though I have been working with computers for nearly 40 years. I use apps on Cloudron, configure them to suit my specific use cases, explore new technologies, and orchestrate free and open-source tools to move closer to achieving digital sovereignty.

Waiting for new apps to appear in the Cloudron app catalog is one way to address missing features. Last weekend, I decided to explore what it takes to get a new app up and running.

In this forum post, I shared my thoughts on a missing piece for de-Googled Android phones: a FindMyDevice server. To be fair, I’ve never lost a phone or been robbed, but having everything set up just in case gives me peace of mind.

I had heard of Docker before and experimented with Dockerfiles and docker-compose. I also tried out the beginner apps mentioned in the Cloudron documentation and, in the past, set up a private registry and the Cloudron build service. So, the mise en place was already done before getting started. The rest should be easy.

TL;DR: It took me nearly 8 hours to get the app running, ensure it worked properly, and make sure individual configuration changes persisted through app updates.

A large, interactive screen, seemingly suspended in the air or projected onto a wall, dominates the center of the image. This screen displays a complex network diagram, with various colored boxes and lines connecting them, representing data flow and connections within the network. The colors are vibrant, including blues, oranges, and purples. Text blocks are also visible on the screen, which could be descriptions or data associated with the network components.

Basics - where to start

While it’s possible to create an app package using Docker build on your local computer, it’s not the ideal approach. Instead, you’ll want to use the Cloudron Build Service (CBS). The primary reason for choosing CBS is the bandwidth available in the data center where your Cloudron instance is hosted. Building apps involves downloading and uploading large amounts of data, often several gigabytes. This can be especially challenging in Germany, where slow household internet infrastructure makes such tasks cumbersome.

There’s no real need to use a self-hosted Docker registry, but since we can, we go ahead and install the Cloudron Container Registry (CCR) from the Cloudron app store.

You’ll also need a Cloudron instance where you have admin privileges. This allows you to configure the Cloudron instance to use a private registry, install apps, and connect your Cloudron CLI to the instance.

One of the final steps is to install the Cloudron CLI on your personal computer.

Documentation, configuration, first challenge

Follow these links to read about the installation and configuration. Once everything is set up and working, try out one of the ‘Hello World’ examples.

Cloudron CLI - Documentation
Cloudron Build Service - Documentation
Cloudron Container Registry - Documentation
First challenge - hello world app

The moment the ‘Hello World’ app works, it’s time for fireworks! 🎆

Which app to start with?

If you’ve never looked into the complexity of apps, start with a simple one. By “simple”, I mean ideally a Go binary.

Bird’s-eye view of Cloudron app architecture

The typical Cloudron app runs as a Docker container on the Cloudron platform. The app code, which you want to update without affecting user changes, is stored on a read-only partition (/app/code/). User data and allowed changes are stored on a read-write partition (/app/data/).

There are many open-source projects out there that scatter user data across the file system. Dealing with such tools can be challenging, as it requires a deep understanding of the software. A symbolic link can sometimes offer a solution, but managing more than five of them starts to feel completely unmanageable.

Many open-source projects require databases or in-memory data structure servers like Redis. In their Docker Compose files, they typically bundle these services together. Cloudron’s approach is to offer these services as so-called add-ons, which you can activate in your Cloudron manifest. The benefit for users: after installing 10 apps that require a PostgreSQL database, you won’t end up with 10 separate PostgreSQL installations on your server.

Why Go (in my use case)?

I came across the FindMyDevice server and decided to try it out as a Docker Compose installation. Upon inspecting the architecture, I found the following components:

  • A Go binary
  • A SQLite database
  • A configuration file
  • An Nginx example file
  • A complete web UI in a folder

So, it felt feasible as my first personal adventure in app packaging.

Where should it be stored? What questions should you ask?

The Go binary is code and requires no user input, so it goes to /app/code/

The app has no other option for storing data than SQLite, meaning we can’t use a database add-on from the Cloudron platform. Since the app stores user data in the SQLite database, it must be placed in /app/data/.

Some settings in the configuration file depend on the user’s decisions, which is why it has to be stored in /app/data/.

Since the Cloudron app platform handles the full reverse proxy and Let’s Encrypt flow, we don’t need the Nginx example file. /dev/null is your friend.

I decided not to dive into the complexities of web UIs, multilingual support, and all the potential feature requests (especially once users realize what’s possible), so I placed the entire folder in /app/code/.

How to start?

I decided to work with a real-world (and live) app package. In my case, I chose the Vault app. Long story short, it helped, but introduced a lot of unnecessary elements that I didn’t fully understand, so I later handed them over to my friend /dev/null.

To have the same in mind, check out the app package for the FindMyDevice Server

The file structure of an app package

The essentials

Cloudron-Manifest.json

The manifest follows a schema, and most of its structure is self-explanatory. For me, working with the id field was completely new. In the end, it’s simple: normally, you include the app’s domain, but in reverse order.
If you want to package an app that won’t be part of the Cloudron app store, not every property in the manifest is required. However, httpPort is mandatory because it’s used by the reverse proxy functionality and for the health check.

Dockerfile

The Dockerfile is straightforward: start with the base image, define and create the working directory, and use wget to download the package from GitLab. Unzip it into the working directory, remove unnecessary files, copy the relevant files into /app/pkg, and set up the start command.

config.yml

Nearly every app requires some configuration. In my case, I found the project’s config.example.yml file.

UnixSocketPath, UnixSocketChmod, PortSecure, and PortInsecure don’t make sense in a Cloudron context. The rest of the configuration should be left for the user to customize with their own values.

During my hours of packaging the app, I realized that the server wasn’t capable of using the httpPort defined in the manifest. Instead, it defaulted to a random port, which wasn’t ideal for the intended goal. In the end, I had to add the PortInsecure variable and set it to match the port specified in the manifest.

Since we can, we often tweak defaults. However, to avoid frustrating users, it’s important to address potential support issues—such as the fact that only 8080 works as the PortInsecure variable in my package.

start.sh
To be fair, the hardest part was feeling like I needed to be a developer to better understand the underlying magic.

set -eu ensures the script crashes when something is undefined instead of running with unspecified behavior. Since we chose localstorage as an add-on in the manifest, we need to create the /app/data folder to store user or app-specific data.

The if-then-fi loop checks whether config.yml exists. If it doesn’t, the file is copied to the specified location.

yq eval -i ".PortInsecure=8080" /app/data/config.yml is part of the magic I hadn’t encountered before. From what I understand, it searches for the PortInsecure value in the config file and updates it to the correct port. Since every change to the config requires an app restart, and start.sh runs on each restart, the user can modify and save a different port—but not in a persistent state. (potential support issue solved.)

chown -R cloudron:cloudron /app/data changes the owner of the files to the correct user.

The exec gosu cloudron:cloudron /app/code/findmydeviceserver serve --db-dir=/app/data --config=/app/data/config.yml --web-dir=/app/code/web starts the server with some configuration options with the correct user.

LICENSE

Every Cloudron app package is released under an open-source license, which is why we include a License file.

more than nice to have

  • DESCRIPTION.md
  • README.md
  • CHANGELOG
  • logo.png
  • screenshots

They’re nice to have, but they become necessary once the app is submitted to the Cloudron app store.

Sanitation

  • .dockerignore
  • .gitignore

Normal in software development.

Challenge accepted and completed!

The moment I took this photo: Unbelievable. The initial package works.

The image is a split screen, showing three distinct parts.The image is a split screen, showing three distinct parts.The leftmost panel shows a portion of a digital map, likely a navigation or location-based app. It displays light green and tan colors representing land areas, and faint, light-colored lines suggestive of roads or paths. A green bar at the bottom shows controls, including arrow buttons (left and right), and a location marker. The central panel is a close-up view of a person, likely taken by a webcam. The person is wearing a dark-hooded sweatshirt or jacket. Only their head and upper shoulders are visible. Their expression is neutral or slightly concerned, and their eyes are clearly visible. The background is a plain, light gray-beige wall, and a decorative chandelier is visible hanging from the ceiling, casting a slight shadow. The rightmost panel is another, smaller map, in a similar style to the left pane. It's also of a geographic location, displaying areas of vegetation indicated by various shades of green and light tan. There is also a very small, indistinct figure, possibly an icon, on the lower right. Overall, the image seems to be from a screen capturing device showing concurrent map applications and a video conference or screen sharing session.

App store

There’s a lot of work to be done after the initial packaging to bring the app to the app store, but that’s a story for another day.

Cloudron

https://www.cloudron.io/get.html
https://forum.cloudron.io/category/4/support