Troubleshooting Your Application
If you're reading this document, it probably means you're quite frustrated and looking for answers. In this guide, we'll give you some common troubleshooting tips and help you get back on track.
Before running any of these commands, you'll need to connect to your server.
View Docker Services
Whenever you deploy a container with Docker, they are grouped into units called "services". Services could be things like databases, web servers, queues, etc.
View Docker services
sudo docker service ls
Learn more about "docker service ls" →
View Docker Service Logs
When you list out your services, you can now view the logs of that service.
If you want to view the logs in real-time, you can use the -f
flag.
View Docker service logs
sudo docker service logs <service_name>
Learn more about "docker service logs" →
Running commands in a running container
Sometimes you might need to do some troubleshooting in a running container. To do this, we need to follow this process:
- Determine the running container ID for the service.
- Use
docker exec
(with a TTY and interactive mode) attach to the shell of the running container.
We will use docker ps
to get the container ID, then use docker exec
to open a shell to the container.
Open a shell to a running container
# Get the container ID of the running service
sudo docker ps --filter "name=<service_name>" --format "{{.ID}}"
# Run a command in the running container (you can replace "sh" with any command)
# but by default, this will open a shell to the container (type "exit" to close)
sudo docker exec -it <container_id> sh
This is a two step process, but if you want to do this in a single command, we can use this trick below:
Open a shell to a running container (single command)
# Replace <service-name> with the name of your service. This will run `sh` in the first container ID it finds.
# You can change `sh` to `bash` if your container. Bash will give you a better experience.
sudo docker exec -it $(sudo docker ps --filter "name=<service_name>" --format "{{.ID}}" | head -n 1) sh
We're able to run this in a single command because we're using a subshell to get the container ID and then passing that to the docker exec
command.
If you don't need a shell and you want to run something like php artisan migrate
, you would just replace sh
with php artisan migrate
. Just make sure your container is running commands relative to the directory you want to run the command in. Sometimes it's best to just open a shell and navigate to the directory you want to run the command in.
Viewing application logs
When you're viewing application logs, you might have multiple places to look depending on the configuration of your application and what output you're trying to look for.
- Docker Swarm Service Logs - The output here will be more server centric. Meaning you'll see deeper services logs such as PHP, FPM, Nginx, etc.
- Application Logs - The output here will be more application centric. Meaning you'll see logs from your application itself.
- Error Tracking Services - If you're using a logging service like Sentry or GlitchTip (which we highly recommend), you'll want to check those logs as well. These services will also alert you if there are any critical errors happening in your application.
Viewing Laravel Logs
If you don't use an error tracking service like Sentry or GlitchTip, Laravel 11 by default will log to the storage/logs/laravel.log
file. You can access that by running a command like this:
View Laravel logs
# Find the service name of your application
sudo docker service ls
# Open a shell to the first container ID returned from your service
sudo docker exec -it $(sudo docker ps --filter "name=<service_name>" --format "{{.ID}}" | head -n 1) sh
# View the available log files
ls storage/logs/
# View the laravel.log file
cat storage/logs/laravel.log
You can see it's a bit of a process to get to the log file, so that's why we recommend using an error tracking service like Sentry or GlitchTip which you can self-host for no cost if you wanted.
Enabling Debug Mode
Depending on the service you're troubleshooting, the official documentation for the Docker image you're using may have instructions on how to enable debug mode.
For example, if you're running serversideup/php:8.4-fpm-nginx
, the official documentation talks about setting LOG_OUTPUT_LEVEL=debug
.
In this example, we would just send up a commit with the environment variable set.
Only use debug modes if you're actively troubleshooting an issue. Debug mode will log a lot of information to your logs which can slow down your application and use a lot of disk space.
Set the LOG_OUTPUT_LEVEL docker-compose.prod.yml file
services:
php:
image: ${SPIN_IMAGE_DOCKERFILE_PHP}
environment:
LOG_OUTPUT_LEVEL: debug
Services won't start or are not accessible
When services won't start or they are not accessible, they are usually broken down into two categories:
- The container or service fail to start
- The container or service is running, but you can't access it through HTTP/HTTPS
Types of Health Checks
When you're running applications with zero-downtime deployments, everything depends on health checks. There are multiple levels of health checks, so make sure you reference above to determine where your issue might be:
- The Docker Swarm health check (the container or service fails to start)
- The reverse proxy health check (the container or service is running, but you can't access it through HTTP/HTTPS)
Docker Health Check
The Docker health check is a health check that is more native to the container itself. Container authors can set HEALTHCHECK
instructions in their Dockerfile
to control the health of the container.
Health checks can also be overridden by the Docker Swarm service configuration at with the healthcheck
option.
One of the most frustrating things might be you try using the docker service logs
command, but the service doesn't have any logs because the service doesn't exist yet. To get around this, we can use the docker service ps
command to get more information on why a container won't start.
Get more information on why a container won't start
sudo docker service ps --no-trunc <service_name>
The above command will show a glimpse of why a container won't start. If everything looks normal there, you might want to inspect the service with docker service inspect
to see if there are any other issues.
Inspect a Docker service
sudo docker service inspect <service_name> --pretty
Adding the --pretty
flag will show nice output of the service configuration and you can look for clues to see why the service might not be starting.
Example of a Docker health check failure
task: non-zero exit (137): dockerexec: unhealthy container
If you see an error message like the one above, this means something in the container is causing the Docker health check to fail. This could be caused by a start up script failing, a configuration issue, or something else.
Start up scripts can be tricky to debug because if any of the scripts send a "non-zero exit" code, the Docker health check could fail. Make sure you're properly handling exit codes and testing your scripts in all scenarios.
To get more information, put your container image in debug mode or add verbose logging and try again. You may get more information from docker service logs
or docker service inspect
.
If the container you're debugging is serversideup/php, you can set container image to debug mode by setting the LOG_OUTPUT_LEVEL
environment variable to debug
to get more information.
Reverse Proxy Health Check
If you're trying to access your application through HTTP/HTTPS, but you're getting a 503 or 404 error, it's likely something is wrong with the reverse proxy health check. For our default configurations, we use Traefik as the reverse proxy, so we'll give you a few tips on how to proceed with troubleshooting.
The example below shows a broken configuration. Don't copy from it. Let's learn from it instead. 😃
Example of a broken configuration
services:
php:
image: ${SPIN_IMAGE_DOCKERFILE}
environment:
SSL_MODE: "full"
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.routers.my-php-app.rule=Host(`${SPIN_APP_DOMA1N}`)"
- "traefik.http.routers.my-php-app.entrypoints=websecure"
- "traefik.http.routers.my-php-app.tls=true"
- "traefik.http.routers.my-php-app.tls.certresolver=letsencryptresolver"
- "traefik.http.services.my-php-app.loadbalancer.server.port=8080"
- "traefik.http.services.my-php-app.loadbalancer.server.scheme=http"
# Health check
- "traefik.http.services.my-php-app.loadbalancer.healthcheck.path=/invalid"
- "traefik.http.services.my-php-app.loadbalancer.healthcheck.interval=30s"
- "traefik.http.services.my-php-app.loadbalancer.healthcheck.timeout=1s"
- "traefik.http.services.my-php-app.loadbalancer.healthcheck.scheme=http"
Invalid Host
I have a typo in my Host rule: Host(
${SPIN_APP_DOMA1N})"
Since the variable is SPIN_APP_DOMAIN
, I have a typo and used SPIN_APP_DOMA1N
instead. This would cause a 404
error because the host is invalid and the variable is undefined.
To fix this I would need to fix my typo and ensure SPIN_APP_DOMAIN
is properly defined.
Invalid Port + Scheme
If I look at the documentation of serversideup/php
, you can see I have SSL_MODE: "full"
set in the service configuration. This tells the application to use HTTPS.
However, there are 4 issues with the configuration because of this change:
server.port=8080
: The docs say this should be changed to8443
whenSSL_MODE: "full"
is set.server.scheme=http
: Since we're using HTTPS, this should be set tohttps
.healthcheck.timeout=1s
: This health check timeout is quite low. This could cause a health check failure if the application takes longer than 1 second to start.healthcheck.scheme=http
: The health check definition is different from the server definition. The health check should usehttps
.healthcheck.path=/invalid
: If I don't have a page at/invalid
, this will cause a health check failure. I can read the serversideup/php docs to set this to/healthcheck
to use the native container health check endpoint or/up
if I am running Laravel.
Wow, you can see how many moving parts it takes to get zero-downtime deployments working. This process is true regardless of any service that you're running, so it's important to understand how each piece works together.
Always remember:
- Start with the service, check to make sure your application is actually getting deployed
- Then check the reverse proxy configuration.
- The health check MUST return a
200
status code to pass. Things like301
or302
redirects can cause the health check to fail. - Always refer to the official documentation of the Docker image you're using for looking for proper health check definitions.
If you want the "easy button" and you want a turn-key solution, look into Spin Pro. We include everything you need to deploy Laravel quickly, reliably, and easily. Everything we talk about above is configured for you automatically.
Database connection issues
A common thing we also see are people who have connection issues. This is usually caused by:
- The database service isn't ready by the time a script in your other container tries to access it (controlled by container start orders).
- The host is invalid, like
localhost
or127.0.0.1
, instead of using the name of the database service in their Docker Compose file (likepostgres
). - The credentials in the
.env
file get changed and the application can't connect to the database.
If you're provisioning a database like Postgres, MariaDB, or MySQL, they make an environment variable available called POSTGRES_USER
andPOSTGRES_PASSWORD
(or similar) that you can use to connect to the database.
Any of these *_USER
or *_PASSWORD
variables will provision on initialization only. If you change your .env
to use a new password, the database will not automatically update the password.
To fix this, you'll need to reference the documentation of your database engine to manually change the password.
Diagnosing connection issues between services
If you notice connection errors between services, make sure your .env
file has the correct values. Special things to double check:
- Ensure your
DB_HOST
(or comparable likeREDIS_HOST
) is set to the service name (ie.mariadb
,redis
, and NOT127.0.0.1
orlocalhost
) - Ensure your environment variable values don't have any weird special characters in them.
- Ensure your
APP_URL
in.env.<environment>
is set correctly (ie.https://app.example.com
). - Ensure any services have strong passwords set. Sometimes default passwords are rejected for security reasons. You can use a number of online tools to generate a strong password for your service.
Stopping a service
If you need to stop a service, you can use the docker service rm
command. For example, to stop a service named my-service
, you can run:
Stop a service
sudo docker service rm my-service
Replace my-service
with the name of the service you want to stop.
Removing volumes
Volumes are used to persist data in your containers. If you need to remove a volume, you can use the docker volume rm
command. For example, to remove a volume named my-volume
, you can run:
Remove a volume
sudo docker volume rm my-volume
Replace my-volume
with the name of the volume you want to remove. You can find the volume name by running sudo docker volume ls
.
Removing Swarm configurations
If you need to remove a Swarm configuration, you can use the docker config rm
command. For example, to remove a configuration named my-config
, you can run:
Remove a configuration
sudo docker config rm my-config
Replace my-config
with the name of the configuration you want to remove. You can find the configuration name by running sudo docker config ls
.
Removing networks
If you need to remove a network, you can use the docker network rm
command. For example, to remove a network named my-network
, you can run:
Remove a network
sudo docker network rm my-network
Pruning unused resources
If you need to remove unused resources, you can use the docker system prune
command. For example, to remove all stopped containers, unused networks, and dangling images, you can run:
Prune unused resources
sudo docker system prune
This command removes all stopped containers, unused networks, and dangling images. You can add the --all
flag to remove all unused images as well.
This will free up disk space on your server.
Starting over
We've all been there. The process of learning sometimes can start with a "redo". The good news is since everything is containerized, everything is designed to be disposable and repeatable.
Since we're running Docker, we do not need to rebuild the server. We can simply remove the Docker data and start over with a fresh deployment.
The commands below will destroy all data. This means things like databases, logs, etc will all be deleted and you'll have a fresh Docker environment to deploy to.
Delete all docker data and start over
sudo docker service rm $(sudo docker service ls -q) # Remove all services
sudo docker stop $(sudo docker ps -aq) # Stop all containers
sudo docker rm $(sudo docker ps -aq) # Remove all containers
sudo docker system prune --all # Remove all unused images and networks
sudo docker volume rm $(sudo docker volume ls -q) # Remove all volumes
sudo docker config rm $(sudo docker config ls -q) # Remove all configurations
Run the commands above individually to ensure everything is removed properly. Once you've verified everything is removed, you can deploy a fresh copy of your application.