Troubleshooting and advanced topics
How it works behind the scenes
To understand and be able to troubleshoot issues when deploying your app, it helps to know a bit about what is going on in the backend. This is what happens on a high level during a successful app creation:
- Git repository is cloned at whatever revision was specified
- The language of the app gets detected
- The app is being built and package into a OCI container with language-appropriate tools
- The built app image is uploaded to the Deploio image registry
- A release is created which executes the previously built image
- The application is exposed on an HTTP endpoint
The build and release step is where most things can go wrong. If there's an
error or incompatibility within the cloning or build step, nctl
will show the
build logs to help with troubleshooting. If the build is successful but a
release does not become ready, usually there should be an indication about the
problem in the app logs. Of course if your app fails silently on start, the app
log might be empty.
Retry build
The build may fail due to multiple reasons. Most of the time a change on the
application code or a missing environment/build variable will fix the issue, but
it might happen that some temporary internal system issue failed the build. In
those cases you can retry the build by using nctl
:
$ nctl update app go-example --retry-build
Retry release
The retry release trigger allows you to manually initiate a new release without requiring code changes or rebuilding your application. This is especially useful when a release has previously failed due to external factors, infrastructure issues, or temporary network failures. For example, if your release process failed due to a broken or incorrect database migration, and you've manually resolved this at the database level, you can directly use the retry release feature to redeploy the existing build:
$ nctl update app go-example --retry-release
Inspecting and running the build output locally
As the build output is just an OCI/Docker image, it's possible to pull the image to your local machine to inspect it. This can be helpful if the build succeeds but the release fails to start the app. Note that it's just possible to pull images for debugging, pushing images to the registry is disabled.
$ nctl get build go-example-build-1 --pull-image
Pulling image of build go-example-build-1
739cb80086f0: Download complete
1bfd697887fa: Download complete
aea73d1e202c: Download complete
e4fd482e7de7: Download complete
✓ Pulled image deploio.296dc8b.registry.nineapis.ch/nine/go-example 💾
After downloading the image, you can interact with it using Docker or Podman (or anything that can interact with OCI images) to inspect and run the image locally.
$ docker run deploio.296dc8b.registry.nineapis.ch/nine/go-example
Executing a shell or command in a Deploio application
You can start a shell in your Deploio application once it was released successfully. Please note that your application needs to already run in a stable way so that a shell can be started. If your application constantly restarts because of an error, starting a shell will not be possible.
You can start a shell with the help of nctl
. This will start a shell in the
first Deploio replica which can be found.
$ nctl get app
NAME HOSTS UNVERIFIED_HOSTS
rails rails.7f0b629.deploio.app none
$ nctl exec app rails
cnb@rails-5fcc67cc-89fd2:/workspace$
You can also start a single command by separating it with --
.
$ nctl exec app rails -- hostname
rails-5fcc67cc-89fd2
Please note, that every command you start will be counted towards your memory quota which depends on your configured Deploio application size. If too much memory is used the whole Deploio application replica will be terminated and you will most probably see an exit code of 137:
nctl: error: command terminated with exit code 137
Similarly, If your application exceeds its local Ephemeral Storage limit, it is immediately terminated. The session is forcibly cut off, and the following error message appears:
nctl: error: command terminated with exit code 137
Furthermore, any change to local files being done in a shell session will be lost, once the Deploio replica was restarted.
Lets Encrypt certificates
Applications on Deploio are exposed via a random generated URL by default. That
way you can access your application during development time without the need for
an own domain name. The default URL is also secured by a Lets Encrypt TLS
certificate which gets directly generated when you create a Deploio
application. For Lets Encrypt certificates, the HTTP-01 challenge
type is
generally used. You can see the status of the default URLs Lets Encrypt
certificate via nctl
:
nctl get app <APP NAME> -o yaml
kind: Application
apiVersion: apps.nine.ch/v1alpha1
...
status:
atProvider:
...
defaultHostsCertificateStatus: Issued
Whenever you add custom host names to your
Deploio application, a corresponding Lets Encrypt TLS certificate will be
issued for all your custom host names. You can see the status of that
certificate via nctl
as well:
nctl get app <APP NAME> -o yaml
kind: Application
apiVersion: apps.nine.ch/v1alpha1
...
status:
atProvider:
...
customHostsCertificateStatus: Pending
As we are using the Lets Encrypt HTTP-01 challenge type, the certificate will only be successfully issued once all of your custom hostnames point to the Deploio infrastructure. We are using an optimized DNS resolving path to quickly react to DNS changes, but it might still take some minutes before the certificate can be issued. This is especially important if you migrate an application which is elsewhere hosted to Deploio. As the transition can take some minutes, please consider a migration during non-business hours.
Please also keep in mind that Lets Encrypt favors IPv6 DNS entries over IP4 ones. So if you have DNS AAAA records for your custom hostnames, make sure to delete them when migrating to Deploio (as Deploio does not support IPv6 currently).
Local Ephemeral Storage limitations for Deploio applications
Ephemeral Storage is a temporary storage mechanism used by your app, or more precisely, by its container while it is running. Currently we limit local Ephemeral Storage to 2 GiB per Deploio application. These limits are in place to prevent any application from consuming excessive resources, ensuring a fair distribution across the system.
What constitutes Ephemeral Storage?
Containers use local Ephemeral Storage for scratch space, caching, and for logs.
Writing data through an exec
session
into a running app's filesystem also counts toward the total Ephemeral Storage
usage. Exceeding the storage limit may cause the app to restart, resulting in
the loss of all Ephemeral Storage data.
Best practices for managing Ephemeral Storage
For effective Ephemeral Storage management, users should minimize writes to the writable layer. Excessive logging and unnecessary temporary file generation should be avoided. Critical data should be stored in persistent storage to prevent loss.
Ephemeral Storage limit does not include the size of the Deploio application image.
What happens when Ephemeral Storage is exceeded?
If an application surpasses the Ephemeral Storage limit, its container is
terminated, and the exit code 137
is returned upon termination.
Impact on exec
sessions
If an exec
session
is active inside a container that exceeds its Ephemeral Storage limit, the
container is immediately terminated. The session is forcibly cut off, and the
following error message appears:
nctl: error: command terminated with exit code 137
The writable layer of the Deploio application
The writable paths and behavior in the app container depend on whether the application was built using Buildpacks (default) or a Dockerfile. Different approaches result in different restrictions and permissions for writable directories.
Applications built using Buildpacks (default)
Buildpacks define a strict set of writable paths based on conventions designed to enhance security and reproducibility. In most Buildpack-built apps, writable paths are restricted to specific directories, commonly:
/workspace
/tmp
Why this restriction? Buildpacks are designed to create immutable layers for application dependencies and base files. Any modifications at runtime should only occur in ephemeral directories to maintain container integrity.
The writable directories in a Buildpack-built container are bound by the app's Ephemeral Storage limit, meaning excessive writing can exhaust available space.
Applications built using a Dockerfile
When an application is built using a Dockerfile, writable paths and behavior depend on the developer's choices, and configurations can vary widely based on application needs. Similar to Buildpacks, writable paths in a Dockerfile-built application are also limited by the app's Ephemeral Storage restrictions.
Checking Which User is Running the Container
Before testing writability, it is helpful to determine which user is running the container. This can be done executing:
id
Example output:
uid=1000(cnb) gid=1000(cnb) groups=1000(cnb)
The User ID may differ between build time and runtime, depending on the Buildpack architecture. Some Buildpacks may use different users during build and runtime, while others may maintain the same user throughout. This may result in different runtime permissions. For more info see Cloud Native Buildpack policy. Dockerfile-based builds allow full control over the user and configurations can vary significantly.
Determining writable directories in your app
First, you need to start a shell in your Deploio application. There is no direct command to list only writable directories, but some exploration can help identify them. Below are some tips that may be helpful.
A useful command to check for writable permissions is:
find / -writable -type d 2>/dev/null | sort
This command may not be available in all container images. If find
is missing
but ls
is present, directory permissions can be inspected manually using:
ls -ld /path/to/directory
If unsure about writable directories, you can also test them manually by attempting to create a file:
touch /tmp/testfile && echo "/tmp is writable" || echo "/tmp is not writable"
If touch is unavailable, writing to a file can be tested with echo:
echo "test" > /tmp/testfile && echo "/tmp is Writable" || echo "/tmp is NOT Writable"
rm -f /tmp/testfile
You can also try mkdir
:
mkdir /tmp/testdir && echo "/tmp is Writable" || echo "/tmp is NOT Writable"
rmdir /tmp/testdir
In some Deploio application images, especially those built using a Dockerfile
from distroless
or scratch
base (for example our
sample Dockerfile app),
the shell may not be included, preventing any command from being executed. In
such cases, you should use alternative debugging tools and techniques.