Skip to main content

Cheat sheet

This cheat sheet covers Cloudron-specific considerations, caveats, and best practices for packaging apps.

Dockerfile.cloudron

If your project already has a Dockerfile, name the Cloudron-specific Dockerfile Dockerfile.cloudron or cloudron/Dockerfile.

Examples

Existing app packages are tagged by framework and language. Ask for help in the forum.

Filesystem

Read-only

The app container has a read-only file system. Writing at runtime to any location other than those listed below produces an error:

DirDescription
/tmpUse this location for temporary files. Files in this directory are cleaned up periodically.
/runUse this location for runtime configuration and dynamic data. These files do not persist across app restarts (for example, after an update or a crash).
/app/dataUse this location to store app data that is to be backed up. To use this location, you must use the localstorage addon.
Other pathsNot writable

Suggestions for creating the Dockerfile:

  • Install required packages in the Dockerfile.
  • Create static configuration files in the Dockerfile.
  • Create symlinks to dynamic configuration files (e.g., a generated config.php) under /run in the Dockerfile.

One-time init

Apps often require initialization on first install. Use the database or filesystem to track initialization state. For example, create a /app/data/.initialized file. Only /app/data persists across restarts and updates.

if [[ ! -f /app/data/.initialized ]]; then
echo "Fresh installation, setting up data directory..."
# Setup commands here
touch /app/data/.initialized
echo "Done."
fi

File ownership

When storing files under /app/data, change file ownership to match the app's user ID before the app starts. Backups, updates, and restores can reset ownership. For example, if the app runs as the cloudron user:

# Change ownership of files
chown -R cloudron:cloudron /app/data

# Start the app
gosu cloudron:cloudron npm start

For Apache+PHP apps, use www-data:www-data instead.

Start script

Many apps use a start.sh script (named by convention; any name works) as the entry point instead of launching the server directly.

Add your start script (start.sh) at the end of the Dockerfile and set it as the default command. Mark start.sh as executable in the app package repo with chmod +x start.sh.

ADD start.sh /app/code/start.sh
CMD [ "/app/code/start.sh" ]

Non-root user

The start.sh script runs as root. Commands like chown require root privileges. However, always run the app itself with the least required permissions to maintain security.

Use the gosu tool to run a binary with a specific user/group:

/usr/local/bin/gosu cloudron:cloudron node /app/code/.build/bundle/main.js

Environment variables

These environment variables are available at app runtime:

NameDescription
CLOUDRONSet to '1'. This is useful for writing platform-specific code
CLOUDRON_ALIAS_DOMAINSSet to the domain aliases. Only set when multiDomain flag is enabled
CLOUDRON_API_ORIGINSet to the HTTP(S) origin of the API. For example, https://my.example.com
CLOUDRON_APP_DOMAINThe domain name of the app. For example, app.example.com
CLOUDRON_APP_ORIGINThe HTTP(s) origin of the app. For example, https://app.example.com
CLOUDRON_DEBUGSet to '1' when the app is in recovery mode
CLOUDRON_PROXY_IPThe IP address of the reverse proxy. Apps can trust the HTTP headers (like X-Forwarded-For) for requests originating from this IP address.
CLOUDRON_WEBADMIN_ORIGINThe HTTP(S) origin of the dashboard. For example, https://my.example.com

Set custom environment variables with cloudron env.

Logging

Apps stream their logs to stdout and stderr. Logging to stdout has several advantages:

  • No log rotation needed — the platform manages logs.
  • No special mechanism needed to release log file handles on rotation.
  • Better integration with tooling like the CLI.

Not all programs log to stdout (e.g., Apache). For these, log to a subdirectory in /run (two levels deep) into files with a .log extension. These logs are autorotated.

Multiple processes

Docker natively restarts crashed processes, so single-process applications do not require a process manager.

Use supervisor, pm2, or any other process manager for applications with more than one component. This excludes web servers like Apache and Nginx, which already manage their child processes. Pick a process manager that forwards signals to child processes.

Supervisor

Configure Supervisor to send app output to stdout:

[program:app]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

Memory limit

By default, apps get 256MB RAM (including swap). Adjust this with the memoryLimit field in the manifest.

Design your app runtime for concurrent use by hundreds of users. The platform does not support concurrent access by thousands of users.

Determine the memory limit by reading /sys/fs/cgroup/memory/memory.limit_in_bytes on cgroups v1 or /sys/fs/cgroup/memory.max on cgroups v2. For example, to spin one worker for every 150M RAM available to the app:

if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then # cgroup v2
memory_limit=$(cat /sys/fs/cgroup/memory.max)
[[ "${memory_limit}" == "max" ]] && memory_limit=$(( 2 * 1024 * 1024 * 1024 )) # "max" really means unlimited
else
memory_limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) # this is the RAM. we have equal amount of swap
fi
worker_count=$((memory_limit/1024/1024/150)) # 1 worker for 150M
worker_count=$((worker_count > 8 ? 8 : worker_count )) # max of 8
worker_count=$((worker_count < 1 ? 1 : worker_count )) # min of 1

SIGTERM handling

Bash does not forward signals to child processes by default. A SIGTERM sent to the parent process does not reach the children. Use exec as the last line of start.sh. Programs like gosu, Nginx, and Apache handle SIGTERM correctly.

For example, start Apache with exec:

echo "Starting apache"
APACHE_CONFDIR="" source /etc/apache2/envvars
rm -f "${APACHE_PID_FILE}"
exec /usr/sbin/apache2 -DFOREGROUND

Debugging

Inspect the filesystem of a running app with cloudron exec.

When an application keeps restarting due to a bug, cloudron exec may fail or disconnect repeatedly. Use cloudron debug instead. In debug mode, the container's file system is read-write, and the app pauses without running the RUN command specified in the Dockerfile.

Disable debug mode with cloudron debug --disable.

Apache

Apache requires configuration changes for the Cloudron environment. The following commands:

  • Disable all default sites
  • Print errors into the app's log and disable other logs
  • Limit server processes to 5 (good default value)
  • Change the port number to the default 8000
RUN rm /etc/apache2/sites-enabled/* \
&& sed -e 's,^ErrorLog.*,ErrorLog "/dev/stderr",' -i /etc/apache2/apache2.conf \
&& sed -e "s,MaxSpareServers[^:].*,MaxSpareServers 5," -i /etc/apache2/mods-available/mpm_prefork.conf \
&& a2disconf other-vhosts-access-log \
&& echo "Listen 8000" > /etc/apache2/ports.conf

Then add your site configuration to Apache:

ADD apache2.conf /etc/apache2/sites-available/app.conf
RUN a2ensite app

Start Apache in start.sh:

echo "Starting apache..."
APACHE_CONFDIR="" source /etc/apache2/envvars
rm -f "${APACHE_PID_FILE}"
exec /usr/sbin/apache2 -DFOREGROUND

Nginx

Nginx serves as a reverse proxy in front of the application, dispatching to different backends based on the request route or other characteristics. Run Nginx and the application through a process manager like Supervisor.

Example Nginx Supervisor configuration:

[program:nginx]
directory=/tmp
command=/usr/sbin/nginx -g "daemon off;"
user=root
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

The base image provides a default Nginx configuration. Add an application-specific config file under /etc/nginx/sites-enabled/ when building the Docker image:

ADD <app config file> /etc/nginx/sites-enabled/<app config file>

The base image uses the unpatched Ubuntu Nginx configuration. Configure the app to use /run/ instead of /var/lib/nginx/ to support the read-only filesystem.

Example Nginx app configuration:

client_body_temp_path /run/client_body;
proxy_temp_path /run/proxy_temp;
fastcgi_temp_path /run/fastcgi_temp;
scgi_temp_path /run/scgi_temp;
uwsgi_temp_path /run/uwsgi_temp;

server {
listen 8000;

root /app/code/dist;

location /api/v1/ {
proxy_pass http://127.0.0.1:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}

PHP

PHP stores session data in /var/lib/php/sessions, which is read-only. Move session data to /run/php/sessions:

RUN rm -rf /var/lib/php/sessions && ln -s /run/php/sessions /var/lib/php/sessions

Create this directory and set its ownership in start.sh:

mkdir -p /run/php/sessions
chown www-data:www-data /run/php/sessions

Java

Java scales memory usage based on available system memory. Inside Docker, Java sees the host's total memory rather than the container's memory limit. Restrict Java to the app's memory limit by adding a JVM parameter:

if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then # cgroup v2
ram=$(cat /sys/fs/cgroup/memory.max)
[[ "${ram}" == "max" ]] && ram=$(( 2 * 1024 * 1024 * 1024 )) # "max" means unlimited
else
ram=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) # this is the RAM. we have equal amount of swap
fi

ram_mb=$(numfmt --to-unit=1048576 --format "%fm" $ram)
export JAVA_OPTS="-XX:MaxRAM=${ram_mb}M"
java ${JAVA_OPTS} -jar ...

See also

The Tutorial covers the full build-install-update development loop.