DEV Community

Cover image for Solved: Is putting images in your Google ads worth it?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Is putting images in your Google ads worth it?

🚀 Executive Summary

TL;DR: Docker containers terminate prematurely when their primary process (PID 1) backgrounds itself and exits. The solution involves configuring the application to run in the foreground or employing a shell wrapper with “wait $!” to ensure PID 1 remains active, correctly tying the container’s lifecycle to the application.

🎯 Key Takeaways

  • A Docker container’s lifecycle is directly tied to its PID 1; if this process exits, the container stops, even if other processes are backgrounded.
  • The ‘Permanent Fix’ involves configuring applications (e.g., Nginx with ‘daemon off;’) to run in the foreground, making them the container’s PID 1.
  • The ‘Shell Wrapper’ fix uses ‘wait $!’ in an entrypoint script to launch background services while keeping PID 1 alive, ensuring proper container shutdown upon application exit.

A Docker container needs a foreground process to stay alive; if your main command forks to the background and exits, the container will stop. Fix this by either running your application in the foreground or using a wrapper script with a blocking command like ‘tail -f /dev/null’.

That Time a Single Ampersand Nearly Caused a Production Rollback

I still remember it. It was 2 AM, we were pushing a major update for a client’s core API, and everything looked green on the CI/CD pipeline. The second the new containers hit the ECS cluster, they just… vanished. They’d start, blink “healthy” for a fraction of a second, and then exit with code 0. No errors, no logs, nothing. The junior on call was frantically redeploying, thinking it was a transient network blip. It wasn’t until I ssh’d into the Docker host and manually ran the container that I saw it: the entrypoint script was ending with /usr/bin/supervisord &. That single ampersand, meant to background the process, was telling Docker, “Hey, I’m done here!” and Docker was dutifully shutting everything down. We’ve all been there, staring at a screen, wondering why something so simple is breaking everything.

The “Why”: Understanding The Docker Lifecycle

Let’s get one thing straight: Docker isn’t a virtual machine. It doesn’t have a traditional init system like systemd or SysVinit running by default. A container’s entire existence is tied to one single process: the one you specify with CMD or ENTRYPOINT in your Dockerfile. This is Process ID 1 (PID 1) inside the container.

If that process finishes, Docker assumes the container’s job is complete and shuts it down. The problem we see so often is when a startup script or command is designed to run as a daemon or a background service. It forks the main process, the parent process (PID 1) exits successfully, and poof, your container is gone.

The Fixes: From “Get It Working Now” to “Do It Right”

Okay, enough theory. You’re here because your service container, let’s call it prod-api-worker-01, is playing hide-and-seek. Here are the three ways I’ve tackled this over the years.

1. The Quick & Dirty Fix: The ‘Tail of Infinity’

This is the duct tape of the Docker world. It’s ugly, you shouldn’t use it in production, but if you need to keep a container alive for five minutes to debug something, it’s your best friend. The idea is to launch your background service and then run a second command that never, ever exits.

You modify your Dockerfile’s CMD like this:

# Dockerfile

# ... your other layers ...

# The BAD but fast way
CMD /path/to/my/start-script.sh && tail -f /dev/null
Enter fullscreen mode Exit fullscreen mode

Why it works: Your script runs in the background (thanks to the implicit or explicit &), and then the shell executes tail -f /dev/null. This command continuously watches a file that never changes, effectively blocking forever. Since this process never exits, the container stays up. It’s a hack, but it’s a useful one for temporary debugging.

Darian’s Warning: I can’t stress this enough. If I see this in a pull request for a production service, I will reject it. This method can hide the true state of your application. If your actual service crashes, the container will still stay running because tail is perfectly happy. This is a recipe for silent failures.

2. The Permanent Fix: Make Your App Behave

The best solution is always to fix the root cause. Most modern applications designed for containerization have a “foreground” or “no-daemon” mode. Instead of fighting the container, make the application work with the container.

For example, with Nginx, the default is to daemonize. But you can easily tell it not to:

# Dockerfile for Nginx

# ... your other layers ...

# The RIGHT way for Nginx
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

For a custom Python, Node, or Go app, this just means you run the application binary directly. For older services or things like supervisord, check the documentation. There is almost always a flag (like -n or --nodaemon) to keep it in the foreground.

This is the clean, correct, and professional way to solve the problem. Your container’s lifecycle is now directly tied to your application’s process, as it should be.

3. The ‘Shell Wrapper’ Fix: A Robust Compromise

Sometimes you can’t modify the application. Maybe it’s a legacy binary, or you need to run a few setup tasks before the main event. In this case, a well-written entrypoint script is the way to go. This is my go-to pattern for complex containers.

First, create an entrypoint.sh script:

#!/bin/sh
set -e

# Run any setup tasks here
echo "Configuring application..."
# /usr/local/bin/configure-my-app.sh

# Start your main application in the background
/usr/sbin/my-legacy-app &

# Wait for the background process to exit
wait $!
Enter fullscreen mode Exit fullscreen mode

Then, make it executable and call it from your Dockerfile:

# Dockerfile

COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
Enter fullscreen mode Exit fullscreen mode

Why it works: The script launches your app in the background, but then the wait $! command tells the shell to pause and wait specifically for the most recent background process to finish. If my-legacy-app crashes or is stopped, the wait command will exit, which in turn causes the script to exit, and Docker cleanly shuts down the container. It’s the best of both worlds: you get proper process management and a clean shutdown signal.

Summary Table: Choosing Your Weapon

Solution Pros Cons
1. The ‘Tail of Infinity’ Extremely fast for debugging; requires zero application knowledge. Dangerous for production; hides application crashes; feels “hacky”.
2. The Permanent Fix Cleanest, most reliable method; correct container lifecycle management. Requires you to know how to run your app in the foreground; not always possible with legacy apps.
3. The ‘Shell Wrapper’ Fix Very flexible; handles setup tasks well; properly manages process lifecycle. Adds another file/layer of complexity; requires basic shell scripting knowledge.

So next time your container plays dead, don’t just stare at the logs. Think about PID 1. Remember that Docker is just doing exactly what you told it to do. Your job is to make sure you’re telling it to do the right thing.


Darian Vance

👉 Read the original article on TechResolve.blog


☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)