Deploying Server Without Push And Pull To Keep Attention Span
Deploying modified server without build,push,pull, and restart
The Daily Development Nightmare
A part of my project code is running flask server and it is deployed on Kubernetes. And over the engineering, you encounter bug, fix, add new feature, refactor, etc. Let’s say you need to add a single debug statement to see what’s happening. In a local environment, this would take 5 seconds—save file, see output. But in Kubernetes? Well, at least 30 seconds up to minutes (sometimes tens of minutes).
The workflow goes like this: edit code, build Docker image, push to registry (seconds to minutes depending on your internet), restart related deployment(pods), and wait for rollout. By the time you see your debug output, you’ve forgotten what you were investigating. You’ve checked Slack, browsed Reddit, maybe started a completely different task. The context switch overhead is devastating.
This isn’t just about waiting—it’s about how the wait destroys your ability to think about problems iteratively. Debugging becomes a batch process instead of an interactive exploration. You start making larger, riskier changes because the cost of iteration is so high. Your development velocity plummets.
Fix
first trial
I simply tried to kill the flask server process and execute the python command again. But there was a fundamental problem. the Flask process was PID 1 in the container.
CMD ["python", "routing_agent_service.py"]
If I do this in Dockerfile, this process becomes PID 1.
In Docker containers, PID 1 is special. When PID 1 exits, the entire container terminates immediately.
The Breakthrough: Separating Concerns
working solution
Make Flask not PID 1.
Create a lightweight wrapper script that becomes PID 1 and manages the Flask process as a child. The wrapper’s only job is process lifecycle management—starting Flask, restarting it on demand, and handling container shutdown signals.
Bash
start.sh becomes PID 1
#!/bin/bash
# start.sh - Simple wrapper that keeps running even if Flask dies
echo "Starting Flask app wrapper..."
echo "Wrapper PID: $$"
# Function to handle signals
cleanup() {
echo "Received signal, shutting down..."
if [ ! -z "$FLASK_PID" ]; then
echo "Killing Flask PID: $FLASK_PID"
kill -TERM $FLASK_PID 2>/dev/null
wait $FLASK_PID 2>/dev/null
fi
echo "Wrapper exiting"
exit 0
}
# Function to restart Flask
restart_flask() {
echo "Restarting Flask..."
if [ ! -z "$FLASK_PID" ]; then
echo "Stopping current Flask PID: $FLASK_PID"
kill -TERM $FLASK_PID 2>/dev/null
wait $FLASK_PID 2>/dev/null
fi
start_flask
}
# Function to start Flask
start_flask() {
echo "Starting Flask application..."
cd /app
python routing_agent_service.py &
FLASK_PID=$!
echo "Flask started with PID: $FLASK_PID"
}
# Set up signal handlers
trap cleanup SIGTERM SIGINT
trap restart_flask SIGUSR1
mkdir -p /app/logs
start_flask
while true; do
if ! kill -0 $FLASK_PID 2>/dev/null; then
echo "Flask process died, restarting..."
start_flask
fi
if [ -f "/app/.restart_trigger" ]; then
echo "Found restart trigger file, restarting Flask..."
rm -f /app/.restart_trigger
restart_flask
fi
sleep 5
doneR1
and Dockerfile should execute this wrapper process not python directly.
CMD ["/app/start.sh"]
This architecture lets PID 1 never exit: The wrapper script runs indefinitely, so the container never terminates unexpectedly. Clean restart semantics: Killing and restarting Flask is straightforward process management. Fresh code loading: A new Python process loads all updated modules from scratch.
User(me) experience
The implementation was surprisingly minimal. The wrapper script is about 30 lines of bash. The Dockerfile change was a single line.
But the real impact was psychological! When iteration is fast, you debug fast, more productively, and even your debugging fashion(style and pattern) could be changed. You’re willing to add temporary logging, test edge cases, try experimental fixes. You can move more easily to do ‘test and see’ instead of ‘should I do this or that?’ since the overhead of updating the code becomes smaller. Productivity-wise, you stay in the zone instead of constantly context switching. Problems that seemed complex often turned out to be simple once you could explore them iteratively.
For most of people who have reliable and fast internet connection, this might not be interesting. But if not, well, code shipping for remote container can be really annoying and ruin your day.
It also highlighted how much development velocity depends on feedback loop timing. There’s a qualitative difference between 30-second and 15-minute iterations. It’s not just 30x faster—it changes how you think about problems. Fast feedback enables more focus, a more exploratory, experimental development style that often leads to better solutions.