django-celery-beat does not work on vps

I’m trying to run postgres, rabbitmq, celery and celery-beat in a docker-composer:

version: '3.8'

services:
  postgres:
    image: postgres:latest
    container_name: recinto_postgres
    environment:
      POSTGRES_PASSWORD: 1234
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
    ports:
      - "5432:5432"
    volumes:
      - /tmp/database/recinto:/var/lib/postgresql/data
    networks:
      - recinto_network
    restart: always

  rabbitmq:
    image: rabbitmq:4.0-management
    restart: always
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_VHOST: "/"
      RABBITMQ_DEFAULT_USER: "guest"
      RABBITMQ_DEFAULT_PASS: "guest"
    networks:
      - recinto_network

  celery:
    build: .
    command: celery -A recinto worker -l info
    volumes:
      - ./:/code
    depends_on:
      - rabbitmq
    networks:
      - recinto_network

  celery-beat:
    build: .
    command: celery -A recinto worker --beat --scheduler django --loglevel=info
    volumes:
      - ./:/code
    depends_on:
      - rabbitmq
    networks:
      - recinto_network

networks:
  recinto_network:
    driver: bridge

apparently postgres, rabbitmq and celery are working normally, because I can run tasks through the terminal automatically. but when I try to schedule a task it never works.

celery_beat logs:

celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/kombu/utils/objects.py", line 40, in __get__
celery-beat_1  |     return super().__get__(instance, owner)
celery-beat_1  |   File "/usr/local/lib/python3.10/functools.py", line 981, in __get__
celery-beat_1  |     val = self.func(instance)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/celery/beat.py", line 677, in scheduler
celery-beat_1  |     return self.get_scheduler()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/celery/beat.py", line 668, in get_scheduler
celery-beat_1  |     return symbol_by_name(self.scheduler_cls, aliases=aliases)(
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 233, in __init__
celery-beat_1  |     Scheduler.__init__(self, *args, **kwargs)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/celery/beat.py", line 264, in __init__
celery-beat_1  |     self.setup_schedule()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 241, in setup_schedule
celery-beat_1  |     self.install_default_entries(self.schedule)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 363, in schedule
celery-beat_1  |     self._schedule = self.all_as_schedule()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django_celery_beat/schedulers.py", line 247, in all_as_schedule
celery-beat_1  |     for model in self.Model.objects.enabled():
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 400, in __iter__
celery-beat_1  |     self._fetch_all()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 1928, in _fetch_all
celery-beat_1  |     self._result_cache = list(self._iterable_class(self))
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 91, in __iter__
celery-beat_1  |     results = compiler.execute_sql(
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1560, in execute_sql
celery-beat_1  |     cursor = self.connection.cursor()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery-beat_1  |     return func(*args, **kwargs)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 316, in cursor
celery-beat_1  |     return self._cursor()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 292, in _cursor
celery-beat_1  |     self.ensure_connection()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery-beat_1  |     return func(*args, **kwargs)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 274, in ensure_connection
celery-beat_1  |     with self.wrap_database_errors:
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/utils.py", line 91, in __exit__
celery-beat_1  |     raise dj_exc_value.with_traceback(traceback) from exc_value
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 275, in ensure_connection
celery-beat_1  |     self.connect()
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery-beat_1  |     return func(*args, **kwargs)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/base/base.py", line 256, in connect
celery-beat_1  |     self.connection = self.get_new_connection(conn_params)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/utils/asyncio.py", line 26, in inner
celery-beat_1  |     return func(*args, **kwargs)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/django/db/backends/postgresql/base.py", line 277, in get_new_connection
celery-beat_1  |     connection = self.Database.connect(**conn_params)
celery-beat_1  |   File "/usr/local/lib/python3.10/site-packages/psycopg2/__init__.py", line 122, in connect
celery-beat_1  |     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
celery-beat_1  | django.db.utils.OperationalError: connection to server at "localhost" (::1), port 5432 failed: Connection refused
celery-beat_1  | 	Is the server running on that host and accepting TCP/IP connections?
celery-beat_1  | connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused
celery-beat_1  | 	Is the server running on that host and accepting TCP/IP connections?

settings.py

CELERY_BROKER_URL = 'pyamqp://guest:guest@rabbitmq:5672//' 
CELERY_RESULT_BACKEND = 'django-db'

celery.py:

import os

from celery import Celery

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'recinto.settings')

app = Celery('recinto')

app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

I discovered that the error is possibly in my database URL in my .env file.

DATABASE_URL=postgres://postgres:1234@localhost:5432/postgres

When I define the URL as localhost, Celery-Beat cannot access the database. However, when I define it as Postgres, it can:

DATABASE_URL=postgres://postgres:1234@postgres:5432/postgres
However, Django cannot access the database this way. I believe this is because it is not running in a container, but rather on the VPS. How can I have a different URL for both Django and Celery-Beat?

You have a number of different options here.

I think the easiest would be to accept the DATABASE_URL as an environment variable, and create the environment variable settings for each of the individual processes needing it.

1 Like

i tried this way:

settings.py:

default_dburl = 'sqlite:///' + str(BASE_DIR / 'db_sqlite3')
DATABASES = {
    'default': config('DATABASE_URL', default=default_dburl, cast=dburl),
}

CELERY_BEAT_DATABASE_URL = config("CELERY_BEAT_DATABASE_URL", default=None)

if CELERY_BEAT_DATABASE_URL:
    DATABASES['celery_beat'] = dburl(CELERY_BEAT_DATABASE_URL)

.env:

DATABASE_URL=postgres://postgres:1234@localhost:5432/postgres
CELERY_BEAT_DATABASE_URL=postgres://postgres:1234@postgres:5432/postgres

celery.py

import os

from celery import Celery
from django.conf import settings

# Set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'recinto.settings')

app = Celery('recinto')

app.config_from_object('django.conf:settings', namespace='CELERY')

app.conf.update(
    result_backend=settings.CELERY_BEAT_DATABASE_URL,
)

app.autodiscover_tasks()

this didn’t work. I imagine this isn’t enough to change the database url used by celery-beat. I would be grateful if you could help me

Ok, I’m talking about using actual environment variables here.

In your celery configuration section of your docker composer file:

environment:
  DATABASE_URL: ...

(Notice how environment variables are set in the rabbitmq section of that same file.)

Then set the url for the environment in the corresponding method for your Django project. (You don’t show how you’re running Django, so I can’t tell how that would be implemented.)

And in your settings file, get the setting from the environment and not from the env file.

Or, if you’re not comfortable having the actual url in the environment, create a supplemental env file for the celery task, and use the environment variable as a flag to indicate that this supplemental file needs to be read to retrieve the database url.

I’m running django directly on the vps with gunicorn and nginx. I created a .socket and a .service file in /etc/systemd/system/ and an nginx file in /etc/nginx/sites-enabled/. while postgres, celery and celery-beat run in containers

I tried to do what you told me, but I couldn’t access the environment variables defined in docker-compose in settings.py. I tried a few variations of this:

RUNNING_IN_CONTAINER = bool(int(os.getenv("RUNNING_IN_CONTAINER", 0)))
if RUNNING_IN_CONTAINER:
 DATABASE_URL = dburl('postgres://postgres:1234@postgres:5432/postgres')
else:
 DATABASE_URL = config('DATABASE_URL', default=default_dburl, cast=dburl)

DATABASES = {
 'default': DATABASE_URL,
}

my docker-compose:

version: '3.8'

services:
  postgres:
    image: postgres:latest
    container_name: recinto_postgres
    environment:
      POSTGRES_PASSWORD: 1234
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
    ports:
      - "5432:5432"
    volumes:
      - /tmp/database/recinto:/var/lib/postgresql/data 
    networks:
      - recinto_network
    restart: always

  rabbitmq:
    image: rabbitmq:4.0-management
    restart: always
    ports:
      - "5672:5672"  # Porta de comunicação com o RabbitMQ
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_VHOST: "/"
      RABBITMQ_DEFAULT_USER: "guest"
      RABBITMQ_DEFAULT_PASS: "guest"
    networks:
      - recinto_network

  celery:
    build: .
    command: celery -A recinto worker -l info
    volumes:
      - ./:/code
    environment:
      - RUNNING_IN_CONTAINER=1
      - CELERY_BEAT_DATABASE_URL=postgres://postgres:1214@postgres:5432/postgres
    depends_on:
      - rabbitmq
    networks:
      - recinto_network

  celery-beat:
    build: .
    command: celery -A recinto worker --beat --scheduler django --loglevel=info
    volumes:
      - ./:/code
    environment:
      - RUNNING_IN_CONTAINER=1
      - CELERY_BEAT_DATABASE_URL=postgres://postgres:1234@postgres:5432/postgres
    depends_on:
      - rabbitmq
      - postgres
    networks:
      - recinto_network
    environment:
      POSTGRES_PASSWORD: 1234
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres

networks:
  recinto_network:
    driver: bridge

In the celery section - notice the difference between the syntax you’re using and the syntax being used in the rabbitmq section.

In your celery-beat section - you have two different environment sections (I’m not sure if the second overrides the first), and you have the same syntax issue as in the celery section.

1 Like

Side note: For setting environment variables in systemd control files, you would use the Environment setting in the [Service] section of the file.

1 Like

It worked. Thank you very much for your attention.