Chapter 38 Quiz: Deploying Python to the Cloud
Instructions: Select the best answer for each multiple-choice question. For short-answer questions, write 2–4 sentences. Answer key follows all questions.
Multiple Choice (Questions 1–12)
1. Which of the following best describes what a Docker container is?
A) A virtual machine that runs its own kernel alongside your application B) A compressed ZIP file containing your application code and all dependencies C) A running instance of a Docker image, isolated from the host system but sharing the host kernel D) A cloud server provisioned by a provider like AWS or Render
2. In a Dockerfile, why should COPY requirements.txt . appear before COPY . .?
A) Docker requires dependency files to be copied before source files as a syntax rule B) Docker caches each layer; if requirements.txt hasn't changed, the pip install cache is reused on subsequent builds, speeding up rebuilds significantly C) Python cannot find installed packages if they are installed after the source code is copied D) Gunicorn requires requirements.txt to be present before application files
3. An application deployed to Render's Free tier has been idle for 20 minutes. A user visits the URL. What happens?
A) The user sees a 404 error because the service has been deleted B) The user waits approximately 50 seconds while the service "cold starts" (restarts from a sleeping state) C) The user is immediately served because Render keeps all services warm D) Render redirects the user to the Starter plan upgrade page
4. You set FLASK_DEBUG=true in Render's environment variables for your production service. A user intentionally triggers an unhandled exception. What is the security risk?
A) The application will crash and be unavailable until restarted B) Flask logs the user's IP address in the exception report C) The interactive Werkzeug debugger is exposed, allowing arbitrary Python code execution from the browser D) The session cookies become invalid, logging all users out
5. AWS Lambda is most appropriate for which of the following use cases?
A) A Flask web dashboard that must respond to user requests within 200ms B) A script that generates a sales report PDF every Monday at 8 AM and emails it C) A real-time chat application for the Acme Corp sales team D) A web application that allows file uploads up to 500MB
6. Maya's maya_projects.db SQLite database is stored inside a Render container at /app/data/maya_projects.db. She deploys a new version of her application. What happens to the database?
A) Nothing — the database persists because it is inside the container filesystem B) The database is deleted and recreated automatically from the schema file C) Without a Persistent Disk, the container is replaced and the database is lost permanently D) Render automatically backs up container filesystems before each deployment
7. Which of the following correctly describes the relationship between python-dotenv in development and platform environment variables in production?
A) In development, .env files are read; in production, the same .env file is uploaded to the server
B) python-dotenv reads .env in development; in production, os.environ.get() reads values provided by the platform — the application code is unchanged between environments
C) python-dotenv is only used in development; production applications cannot use os.environ.get() without special configuration
D) Environment variables must be loaded differently in development and production, requiring two different versions of the configuration code
8. The chapter recommends pinning exact versions in requirements.txt (e.g., flask==3.0.3 instead of flask>=3.0). Why?
A) Render does not support version ranges in requirements.txt B) Gunicorn requires all packages to have exact versions specified C) Unpinned packages install the latest version at deploy time, which may introduce breaking changes, making deployments unpredictable D) Python's pip command raises an error if version ranges are used in production environments
9. GitHub Actions is described as providing "Continuous Integration." In the context of this chapter's workflow, what specifically does it provide?
A) Automatic code formatting and style correction on every commit B) Automated test execution on every push, failing the workflow (and potentially blocking deployment) if tests fail C) A service that writes tests automatically based on your application's code D) A Render-specific tool that manages deployment configuration
10. The ENV PYTHONUNBUFFERED=1 setting in the Dockerfile is important because:
A) It makes Python run faster by skipping variable name lookups
B) It prevents Python from writing compiled .pyc files to disk
C) Without it, Python may buffer print() and logging output, causing log lines to appear delayed or never appear in container logs
D) It configures Python to use UTF-8 encoding for all string operations
11. On Render, a developer pushes a commit with a Python syntax error to the main branch. CI (GitHub Actions) is not configured. What happens?
A) Render detects the syntax error before building and rejects the commit B) The Docker build fails (because Python would fail to start with a syntax error), and Render keeps the previous version running C) The new container starts successfully but crashes on the first request, taking the service down until the error is fixed and redeployed D) Render automatically rolls back to the previous deployment within 30 seconds
12. What is a "cold start" in the context of cloud deployment?
A) A deployment that begins from a fresh Docker image build rather than a cached image
B) The latency experienced when a serverless function (Lambda) or sleeping container (Render free tier) receives its first request after a period of inactivity
C) The time it takes for a newly deployed container to warm up its database connections
D) The process of running docker build for the first time without any cached layers
Short Answer (Questions 13–17)
13. Explain the command gunicorn --workers 4 --bind 0.0.0.0:8000 app:app. Break down each argument and explain why 0.0.0.0 is necessary inside a Docker container (as opposed to 127.0.0.1).
14. The chapter describes two different reasons why DEBUG=True is dangerous in production. What are they? Are there any scenarios where the risks described would not apply?
15. Maya's SQLite database is backed up daily to S3. Her Render persistent disk fails on a Wednesday at 2 PM. The last backup ran at 3 AM that morning. What data might be lost? How could she reduce this risk further without switching to a managed database?
16. What is the Post/Redirect/Get pattern, and what does it have to do with deployment? (Hint: think about what happens when a load balancer sends two requests for the same form submission to two different server instances.)
17. A colleague says: "I don't need Docker. I just use Render's native Python environment (without a Dockerfile) and it works fine." Is this a reasonable position? What would you identify as the tradeoffs?
Answer Key
Multiple Choice
1. C — A Docker container is a running process (or set of processes) isolated from the host using Linux kernel features (namespaces, cgroups). It is not a virtual machine (VMs have their own kernel, A is wrong). It is not a ZIP file (B). It is not a cloud server itself — it runs ON a cloud server (D).
2. B — Docker builds images in layers, and layers are cached. If requirements.txt has not changed since the last build, Docker reuses the cached pip install layer, skipping all package installation. If COPY . . appeared first, changing any source file would invalidate all subsequent layers including the pip install, forcing a full dependency reinstall on every code change. This is the most practically important optimization in a Dockerfile.
3. B — Render's Free tier automatically suspends services after 15 minutes of no incoming requests. When a new request arrives, the service starts back up — a process that takes approximately 50 seconds. During this cold start, the user waits. This is the primary reason the chapter recommends the Starter tier ($7/month) for any tool that colleagues depend on: it stays running continuously.
4. C — Flask uses the Werkzeug debugger in debug mode. When an unhandled exception occurs with debug=True, Flask serves an interactive debugger page in the browser that allows the user to execute arbitrary Python expressions in any stack frame of the traceback. This is an intentional developer tool that is explicitly documented as dangerous in production.
5. B — Lambda is ideal for scheduled, event-driven scripts with execution times under 15 minutes. A scheduled Monday morning report is the textbook Lambda use case. A real-time web dashboard (A) needs a persistent server. A chat application (C) requires persistent connections. A 500MB file upload handler (D) exceeds Lambda's limits and would require different architecture.
6. C — Without a Persistent Disk, the Render container's filesystem is ephemeral: when the container is replaced (on deployment, restart, or crash), all files written inside the container are gone. This is a critical understanding for any application that writes data. The Render Persistent Disk ($1/month) mounts a durable volume into the container that survives container replacement.
7. B — This is the fundamental design principle: application code reads from environment variables with os.environ.get(). In development, python-dotenv loads values from .env into the environment. In production, the platform (Render, Railway, Lambda) provides the environment variables directly. The application code is identical — it never knows or cares where the values came from. A is wrong (.env files should never be on production servers). C and D are both incorrect.
8. C — Without exact version pinning, pip install -r requirements.txt installs the current latest version of each package. If Flask releases version 4.0 with breaking changes between your test and your deployment, your production deployment breaks for reasons you did not change. Exact version pinning (flask==3.0.3) ensures the deployment uses exactly the same version you tested with, regardless of when it runs.
9. B — GitHub Actions CI automates test execution. The workflow defined in .github/workflows/test.yml runs pytest tests/ on every push. If tests fail, the workflow fails — the red X is visible on the commit. When Render is configured to require passing CI before deploying, this prevents broken code from reaching production.
10. C — Python buffers its output streams (stdout and stderr) by default for performance. In a container, where all application output goes to stdout/stderr to be captured by the Docker logging driver, buffering means log lines may be held in memory and only flushed when the buffer fills up — causing delayed or missing log output, especially if the application crashes. PYTHONUNBUFFERED=1 flushes output immediately. Note: B describes PYTHONDONTWRITEBYTECODE=1, not PYTHONUNBUFFERED.
11. B — In a well-configured Docker build, the CMD is gunicorn ... app:app. Gunicorn imports the app module at startup. A Python syntax error in app.py causes the import to fail, which causes Gunicorn to fail to start, which causes the Docker health check to fail, which causes Render to keep the previous container running. The deployment shows as "failed" in Render's dashboard, but the service remains available on the previous version. This is one of the protections that proper Dockerfile + Gunicorn setup provides.
12. B — Cold start refers to the latency of waking up a system that has been idle. For Lambda, functions are "frozen" when not in use and must be re-initialized on first invocation after a quiet period. For Render Free tier, services sleep after 15 minutes of inactivity and must restart. Both result in the first request after idle being significantly slower than subsequent requests. "Warm" instances (Render Starter, provisioned Lambda concurrency) avoid cold starts at higher cost.
Short Answer — Model Responses
13. gunicorn — runs the Gunicorn WSGI server. --workers 4 — spawns 4 worker processes; each handles one request at a time, so 4 concurrent requests are handled simultaneously. Rule of thumb: 2× CPU cores + 1. --bind 0.0.0.0:8000 — tells Gunicorn to listen on port 8000 on all network interfaces. app:app — instructs Gunicorn to look in app.py (first app) for the Flask object named app (second app).
0.0.0.0 is necessary inside a Docker container because 127.0.0.1 (localhost) would only accept connections from within the container itself. The host machine (and any users connecting to the container) connects through the container's network interface, not localhost. Binding to 0.0.0.0 accepts connections from all interfaces, including the one mapped by Docker's -p 8000:8000 flag.
14. First: debug=True enables Flask's interactive Werkzeug debugger. When an unhandled exception occurs, the debugger serves a page that allows execution of arbitrary Python code in the browser. Any person who can reach the URL can run any Python on the server. Second: debug=True enables the auto-reloader, which monitors the filesystem for changes. This is a minor additional attack surface but more importantly signals that the application is not intended for production.
The risks would not apply if the application were completely inaccessible to anyone except the developer — for example, running on localhost only (not accessible from other machines on the network). In that case, there is no attacker who can reach the debugger URL. However, even on a home network, debug=True in production-intended deployments is never correct practice.
15. Data loss risk: up to 11 hours of time entries, milestone updates, and any other database writes made between 3 AM (backup) and 2 PM (failure). At Maya's scale, this could be several hours of time entries and any project status updates she made that morning. To reduce this risk without switching databases, Maya could increase backup frequency to every 4–6 hours, or implement write-ahead logging with streaming backup to S3 using SQLite's backup API. A further step is switching to PostgreSQL with Render's managed database, which provides continuous WAL archiving with point-in-time recovery.
16. The PRG pattern (after a successful POST, redirect to a GET) prevents duplicate form submissions when users refresh the page. In a multi-server deployment (load balancer distributing requests to multiple server instances), it also prevents a subtle issue: if a POST is sent to Server A and the redirect is to a GET that arrives at Server B, Server B only needs to render the confirmation page — it does not need to re-run the form processing logic. Without PRG, if two requests for the same form submission arrived at different servers simultaneously (possible under load), both might process the submission, creating duplicates. PRG ensures the "do the work" step (POST) and the "show the result" step (GET) are cleanly separated.
17. Render's "native Python" environment (without a Dockerfile) is a reasonable choice for simple applications — Render can auto-detect Flask apps and install requirements.txt automatically. The tradeoffs vs. Docker: Render's native environment is Render-specific, so the "works in Docker locally, works in Docker on Render" guarantee no longer holds — there may be subtle differences between your local environment and Render's Python environment. Docker adds build time (slower deploys initially) and requires knowing Dockerfile syntax. However, Docker provides stronger guarantees: if it runs in the container locally, it runs identically in the container on Render. For applications that will be deployed to multiple platforms or maintained by multiple developers, Docker's reproducibility is worth the additional complexity. For a single developer deploying to one platform, Render's native Python environment is a practical and acceptable choice.