Celery is a critical tool for building scalable Python applications, allowing you to offload long-running tasks to background workers. But while developing with Celery is straightforward, figuring out your Celery worker deployment strategy can be complex. Many platforms charge you for each running process, meaning your web server and each worker come with separate bills that quickly add up.
On Miget, you can deploy a web application, a Celery worker, and the required message broker inside a single, fixed-price Resource. You only pay for the compute you allocate, not for the number of services you run. This guide will walk you through deploying a Python Flask application and a dedicated Celery worker on Miget, demonstrating how to manage multiple process types without per-service billing.
What You'll Need
Before we start, make sure you have the following:
- A Miget account. The free tier is sufficient for this tutorial.
- A GitHub account.
- Git installed on your local machine.
Step 1: Create a Sample Python Application
First, we'll create a simple Flask application that uses Celery to run a background task. This application will have two main components: a web server to receive requests and a worker to process them.
Create a new directory for your project and add the following files.
Here is the project structure:
.
├── app.py
├── tasks.py
├── Procfile
└── requirements.txt
requirements.txt
This file lists our project's dependencies. We need Flask for the web app, celery for the worker, and valkey to connect to the message broker.
Flask==3.0.3
celery==5.4.0
valkey==7.2.0
gunicorn==22.0.0
tasks.py
This file configures the Celery instance and defines a simple background task. Celery requires a message broker to queue tasks-we'll use a managed Valkey (Redis-compatible) Addon provided by Miget. The connection URL will be injected into our environment as VALKEY_URL.
import os
import time
from celery import Celery
# Get the Valkey URL from environment variables, with a default for local dev
valkey_url = os.environ.get('VALKEY_URL', 'redis://localhost:6379/0')
# Initialize Celery
celery_app = Celery(
'tasks',
broker=valkey_url,
backend=valkey_url
)
@celery_app.task
def long_running_task(x, y):
print(f"Task started: Adding {x} and {y}")
time.sleep(10) # Simulate a 10-second task
result = x + y
print(f"Task finished: {x} + {y} = {result}")
return result
app.py
This is our main Flask application. It has a single endpoint that triggers our long_running_task.
from flask import Flask, jsonify
from tasks import long_running_task
app = Flask(__name__)
@app.route('/')
def index():
return "Web app is running. Use /start-task to queue a job."
@app.route('/start-task')
def start_task():
# Trigger the background task
task = long_running_task.delay(5, 10)
return jsonify({
"message": "Task has been submitted to the background worker.",
"task_id": task.id
}), 202
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Procfile
A Procfile declares the commands that are run by your application's containers. Our open-source migetpacks build system uses it to understand how to run your app. Here, we define two process types: web for the Gunicorn server and worker for the Celery process.
web: gunicorn app:app --bind 0.0.0.0:5000
worker: celery -A tasks worker --loglevel=INFO
With these files in place, push your code to a new GitHub repository.
Step 2: Set Up Your Miget Project
Now, let's configure the infrastructure on Miget.
-
Create a Project and Resource: Log into the Miget dashboard and create a new Project. Inside the project, create a Resource on the free or dev plan. This Resource provides the CPU, RAM, and disk for all our services.
-
Create the Web App: Inside your project, create a new App. Connect it to the GitHub repository you just created. Miget will automatically detect the Python project and configure the build using
migetpacks. You don't need a Dockerfile. -
Add a Valkey Message Broker: Your web app needs a message broker for Celery.
- Navigate to your new web app in the Miget dashboard.
- Go to the Addons tab.
- Click "Add Addon" and select Valkey.
- Miget will provision a Redis-compatible Valkey instance and automatically inject the connection string as the
VALKEY_URLenvironment variable into your web app.
At this point, Miget will trigger the first deployment. It will read the Procfile, build a container image, and start the web process. Your Flask app is now live.
Step 3: Deploy the Celery Worker App
Next, we'll deploy the Celery worker. Instead of creating an entirely new service from scratch, we'll create a second app that re-uses the same container image built for our web app.
-
Create a New App for the Worker: Go back to your project dashboard and create another new App. Give it a distinct name, like
my-app-worker. -
Configure the Deployment Method: This is the key step. For the deployment method, select Parent Image.
- For the Parent App, choose the web app you created in the previous step.
- This tells Miget to use the exact same image that was built for the web app, skipping a redundant build process.
-
Set the Start Command: The parent image will default to the
webcommand from theProcfile. We need to override it to run our worker.- Go to the Settings tab of your new worker app.
- Under Start Command, enter the command for your worker process:
celery -A tasks worker --loglevel=INFO. - Save the changes.
-
Attach the Valkey Addon: The worker also needs to connect to the message broker. Since Addons are tied to a specific app, you need to share the connection details.
- Go to the web app's Addons tab and view the details of your Valkey addon. Copy the
VALKEY_URLvalue. - Go to the worker app's Environment Variables tab.
- Create a new variable named
VALKEY_URLand paste the connection string.
- Go to the web app's Addons tab and view the details of your Valkey addon. Copy the
Now, deploy the worker app. It will start instantly because it's using the pre-built image from the web app. You now have a web server and a Celery worker running in the same Resource, sharing the same code, and communicating via the Valkey broker.
To test it, visit the /start-task endpoint of your web app. Then, check the logs of your worker app-you'll see the task being received and processed after a 10-second delay.
How Miget Simplifies Python Worker Deployment
This setup highlights several advantages of deploying on Miget, especially for applications with multiple components like a Python background worker.
-
Pay Per Compute, Not Per App: Both the web server and the Celery worker run within the same fixed-price Resource plan. You can add more workers, cron jobs, or other services without incurring extra per-service charges, making your costs predictable.
-
Parent Image Deployments: The parent image feature is designed for this exact use case. It lets you build your application image once and run it with different commands for different roles (web, worker, scheduler). This saves build time and ensures consistency across your services.
-
Managed Addons: With built-in, managed addons for Valkey, PostgreSQL, and MySQL, you don't need to provision or manage a separate message broker or database. It's all included in your Resource plan.
-
Zero-Configuration Builds: Miget's open-source
migetpacksautomatically detect yourrequirements.txtandProcfile, building a production-ready container without requiring you to write or maintain a Dockerfile.
Next Steps
You now have a scalable foundation for your Python application. Both your web app and worker can be scaled independently by adjusting their replica counts, all within the limits of your single Resource plan.
Ready to deploy your own application?
- Read the Miget Python deployment guide.
- Explore more about Parent Image deployments.
- Learn about our managed Addons.
What to read next
- How to Run Cron Jobs on a Modern PaaS - schedule recurring tasks alongside your Celery worker without any extra infrastructure
- How to Deploy Python to Docker Without Writing a Dockerfile - use migetpacks to build any Python app without maintaining a Dockerfile
- Deploy a Django App with PostgreSQL in Minutes - a complete walkthrough for deploying a Python web app with a managed database