More actions
No edit summary |
No edit summary |
||
| (29 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
This (unofficial) guide was made by [https://hackclub.slack.com/team/U094NTBR1S5 @twonum] (feel free to <abbr title="Dungeon Master</joke>Direct Message">DM</abbr> me for help). | |||
This | This guide works with all [https://docs.djangoproject.com/en/5.2/howto/deployment/ WSGI and ASGI servers] <em><strong>that is not</strong> Django's built-in [https://docs.djangoproject.com/en/5.2/ref/django-admin/#django-admin-runserver {{code|runserver}}] command</em>. | ||
; Tools this guide uses | ; Tools this guide uses | ||
* Hypercorn ASGI | * A [https://docs.djangoproject.com/en/5.2/howto/deployment/ WSGI and ASGI server] <em><strong>that is not</strong> Django's built-in [https://docs.djangoproject.com/en/5.2/ref/django-admin/#django-admin-runserver {{code|runserver}}] command</em> | ||
** I'll be using Hypercorn but this works with all WSGI/ASGI servers. | |||
* Caddy reverse proxy and web server with automatic HTTPS | * Caddy reverse proxy and web server with automatic HTTPS | ||
* PostgreSQL relational database for Django's ORM | * PostgreSQL relational database for Django's ORM | ||
| Line 15: | Line 14: | ||
* ~ (/home/orpheus) | * ~ (/home/orpheus) | ||
** project | ** project | ||
*** .venv | |||
*** manage.py | *** manage.py | ||
*** ... | *** ... | ||
| Line 21: | Line 21: | ||
**** static | **** static | ||
**** media | **** media | ||
** project.sh | |||
** Caddyfile | ** Caddyfile | ||
** .config | ** .config | ||
** ... | ** ... | ||
I'll use {{Code|$THIS_VARIABLE_NOTATION}}. Make a mental note of {{Code|export}}s and ''replace'' the pseudo-variables with actual values. | |||
== Getting ready == | == Getting ready == | ||
| Line 34: | Line 36: | ||
== Get Nest resources == | == Get Nest resources == | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
export PROJECT_NAME="your project directory name" # set this to your project's directory name (like "project" in /home/$USERNAME/project) | |||
export PROJECT_NAME="your project directory name" # set this to your project's directory name (like "project" in | nest db create $PROJECT_NAME | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 67: | Line 69: | ||
cd ~/$PROJECT_NAME | cd ~/$PROJECT_NAME | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Set up venv and dependencies == | |||
<syntaxhighlight lang="bash"> | |||
python3 -m venv .venv | |||
source .venv/bin/activate | |||
pip install -r requirements.txt | |||
</syntaxhighlight> | |||
Also, install {{code|psycopg}} and your WSGI/ASGI server. | |||
== Set up Django settings == | |||
=== Use .env files with Django === | |||
Use a .env file. I'll use django-environ: | |||
<syntaxhighlight lang="python3"> | |||
from pathlib import Path | |||
import os | |||
from environ import Env | |||
... | |||
env = Env( | |||
DEBUG=(bool, False), | |||
ALLOWED_HOSTS=(list, []), | |||
) | |||
BASE_DIR = Path(__file__).resolve().parent.parent | |||
environ.Env.read_env(os.path.join(BASE_DIR, '.env')) | |||
def _env_file(key: str) -> str: | |||
env_ = env(key) | |||
if env_[:2] == './': | |||
return str(BASE_DIR / env_[2:]) | |||
return str(Path(env_)) | |||
... | |||
DEBUG = env("DEBUG") | |||
ALLOWED_HOSTS = env('ALLOWED_HOSTS') | |||
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS] | |||
SECRET_KEY = env("SECRET_KEY") | |||
DATABASES = { | |||
'default': env.db(), | |||
} | |||
STATIC_URL = env("STATIC_URL") | |||
STATIC_ROOT = _env_file("STATIC_ROOT") | |||
MEDIA_URL = env("MEDIA_URL") | |||
MEDIA_ROOT = _env_file("MEDIA_ROOT") | |||
... # Use the same format for other options | |||
</syntaxhighlight> | |||
==== What your .env file should look like ==== | |||
<syntaxhighlight lang="bash"> | |||
DEBUG=False | |||
ALLOWED_HOSTS=$PROJECT_DOMAIN | |||
SECRET_KEY=please-change-this | |||
DATABASE_URL=psql://$USERNAME:$NEST_PASSWORD@127.0.0.1:5432/$USERNAME_$PROJECT_NAME | |||
</syntaxhighlight> | |||
==== Some other options to tweak ==== | |||
<syntaxhighlight lang="bash"> | |||
STATIC_ROOT=$HOME/static/$PROJECT_NAME/static | |||
STATIC_URL=/static/ | |||
MEDIA_ROOT=$HOME/static/$PROJECT_NAME/media | |||
MEDIA_URL=/media/ | |||
</syntaxhighlight> | |||
=== Migration and static collection === | |||
<syntaxhighlight lang="bash"> | |||
source .env | |||
python3 manage.py migrate | |||
python3 manage.py collectstatic | |||
</syntaxhighlight> | |||
== Set up the WSGI/ASGI server == | |||
=== Get a port === | |||
<syntaxhighlight lang="bash"> | |||
nest get_port | |||
</syntaxhighlight> | |||
Remember this number and store it in the mental variable {{Code|$PORT}}. | |||
From my experience, the port might get taken quickly. In that case, just get a new port and change the old one in existing files. | |||
This is the ''internal'' port (on Nest); the actual application uses port 443 (the standard HTTPS port). | |||
=== Create the run script === | |||
==== ~/$PROJECT_NAME.sh ==== | |||
{{code|$PACKAGE_NAME}} is the name of the directory with settings.py. | |||
<syntaxhighlight lang="bash"> | |||
#!/bin/bash | |||
cd ~/$PROJECT_NAME | |||
source .venv/bin/activate | |||
# WSGI/ASGI server command | |||
</syntaxhighlight> | |||
Add your WSGI or ASGI server's command. Bind it to {{code|127.0.0.1:$PORT}} with the application name of {{code|$PACKAGE_NAME.wsgi.application}} for WSGI and {{code|$PACKAGE_NAME.asgi:application}} for ASGI. See the [https://docs.djangoproject.com/en/5.2/howto/deployment/ Django deployment page] for info about servers. | |||
===== Example for Hypercorn ===== | |||
<syntaxhighlight lang="bash"> | |||
hypercorn -b 127.0.0.1:$PORT $PACKAGE_NAME.asgi:application | |||
</syntaxhighlight> | |||
== Set up systemd with the run script == | |||
=== ~/.config/systemd/user/$PROJECT_NAME.service === | |||
<syntaxhighlight> | |||
[Unit] | |||
Description=YOUR DESCRIPTION HERE | |||
[Service] | |||
WorkingDirectory=%h/$PROJECT_NAME | |||
EnvironmentFile=%h/$PROJECT_NAME/.env | |||
ExecStart=bash ../$PROJECT_NAME.sh | |||
Restart=on-failure | |||
[Install] | |||
WantedBy=default.target | |||
</syntaxhighlight> | |||
=== Start the service === | |||
<syntaxhighlight lang="bash"> | |||
systemctl --user daemon-reload | |||
systemctl --user enable --now $PROJECT_NAME.service | |||
systemctl --user status $PROJECT_NAME.service | |||
</syntaxhighlight> | |||
== Set up the Caddy server == | |||
=== ~/Caddyfile === | |||
Append this:<syntaxhighlight start="11"> | |||
http://$PROJECT_DOMAIN { | |||
bind unix/.$PROJECT_DOMAIN.webserver.sock|777 | |||
handle /static* { | |||
root * $HOME/static/$PROJECT_NAME | |||
file_server | |||
} | |||
handle /media* { | |||
root * $HOME/static/$PROJECT_NAME | |||
file_server | |||
} | |||
reverse_proxy :$PORT | |||
} | |||
</syntaxhighlight> | |||
=== Restart Caddy service === | |||
<syntaxhighlight lang="bash"> | |||
systemctl --user restart caddy.service | |||
</syntaxhighlight> | |||
== Check it out == | |||
Go to <code>$PROJECT_DOMAIN</code> in your browser. | |||
First load takes some time as Caddy needs to get a certificate from Let's Encrypt. | |||
It should probably work. If not, please Dungeon Master [https://hackclub.slack.com/team/U094NTBR1S5 @twonum] who is at fault for writing this guide. | |||
== Avoiding hiccups == | |||
Make sure that it continues to work; if not, there's probably something wrong with the config. | |||
Latest revision as of 03:00, 25 July 2025
This (unofficial) guide was made by @twonum (feel free to DM me for help).
This guide works with all WSGI and ASGI servers that is not Django's built-in runserver command.
- Tools this guide uses
- A WSGI and ASGI server that is not Django's built-in
runservercommand- I'll be using Hypercorn but this works with all WSGI/ASGI servers.
- Caddy reverse proxy and web server with automatic HTTPS
- PostgreSQL relational database for Django's ORM
- systemd "daemon" (as it's called in Unix) to manage services
I'll assume that orpheus is your username and this is your directory structure:
- ~ (/home/orpheus)
- project
- .venv
- manage.py
- ...
- static
- project
- static
- media
- project
- project.sh
- Caddyfile
- .config
- ...
- project
I'll use $THIS_VARIABLE_NOTATION. Make a mental note of exports and replace the pseudo-variables with actual values.
Getting ready
First, get a shell to Nest. Don't have a Nest account?
ssh orpheus@hackclub.appGet Nest resources
export PROJECT_NAME="your project directory name" # set this to your project's directory name (like "project" in /home/$USERNAME/project)
nest db create $PROJECT_NAMEhackclub.app or custom domain?
$PROJECT_NAME.$USERNAME.hackclub.app (free)
export PROJECT_DOMAIN=$PROJECT_NAME.$USERNAME.hackclub.app
nest caddy add $PROJECT_DOMAINCustom domain (looks better, but you need your own DNS)
Add a CNAME record to $USERNAME.hackclub.app.
If you use Cloudflare or another proxy, turn the proxy off.
Wait and run this to check your DNS record:
export PROJECT_DOMAIN="hello.example.com" # set this
dig $PROJECT_DOMAIN CNAMEThen:
nest caddy add $PROJECT_DOMAINGet your project's files on Nest
Copy the directory containing manage.py to /home/$USERNAME/$PROJECT_NAME. See Quickstart#Using_the_Account.
cd ~/$PROJECT_NAMESet up venv and dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtAlso, install psycopg and your WSGI/ASGI server.
Set up Django settings
Use .env files with Django
Use a .env file. I'll use django-environ:
from pathlib import Path
import os
from environ import Env
...
env = Env(
DEBUG=(bool, False),
ALLOWED_HOSTS=(list, []),
)
BASE_DIR = Path(__file__).resolve().parent.parent
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
def _env_file(key: str) -> str:
env_ = env(key)
if env_[:2] == './':
return str(BASE_DIR / env_[2:])
return str(Path(env_))
...
DEBUG = env("DEBUG")
ALLOWED_HOSTS = env('ALLOWED_HOSTS')
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS]
SECRET_KEY = env("SECRET_KEY")
DATABASES = {
'default': env.db(),
}
STATIC_URL = env("STATIC_URL")
STATIC_ROOT = _env_file("STATIC_ROOT")
MEDIA_URL = env("MEDIA_URL")
MEDIA_ROOT = _env_file("MEDIA_ROOT")
... # Use the same format for other optionsWhat your .env file should look like
DEBUG=False
ALLOWED_HOSTS=$PROJECT_DOMAIN
SECRET_KEY=please-change-this
DATABASE_URL=psql://$USERNAME:$NEST_PASSWORD@127.0.0.1:5432/$USERNAME_$PROJECT_NAMESome other options to tweak
STATIC_ROOT=$HOME/static/$PROJECT_NAME/static
STATIC_URL=/static/
MEDIA_ROOT=$HOME/static/$PROJECT_NAME/media
MEDIA_URL=/media/Migration and static collection
source .env
python3 manage.py migrate
python3 manage.py collectstaticSet up the WSGI/ASGI server
Get a port
nest get_portRemember this number and store it in the mental variable $PORT.
From my experience, the port might get taken quickly. In that case, just get a new port and change the old one in existing files.
This is the internal port (on Nest); the actual application uses port 443 (the standard HTTPS port).
Create the run script
~/$PROJECT_NAME.sh
$PACKAGE_NAME is the name of the directory with settings.py.
#!/bin/bash
cd ~/$PROJECT_NAME
source .venv/bin/activate
# WSGI/ASGI server commandAdd your WSGI or ASGI server's command. Bind it to 127.0.0.1:$PORT with the application name of $PACKAGE_NAME.wsgi.application for WSGI and $PACKAGE_NAME.asgi:application for ASGI. See the Django deployment page for info about servers.
Example for Hypercorn
hypercorn -b 127.0.0.1:$PORT $PACKAGE_NAME.asgi:applicationSet up systemd with the run script
~/.config/systemd/user/$PROJECT_NAME.service
[Unit]
Description=YOUR DESCRIPTION HERE
[Service]
WorkingDirectory=%h/$PROJECT_NAME
EnvironmentFile=%h/$PROJECT_NAME/.env
ExecStart=bash ../$PROJECT_NAME.sh
Restart=on-failure
[Install]
WantedBy=default.targetStart the service
systemctl --user daemon-reload
systemctl --user enable --now $PROJECT_NAME.service
systemctl --user status $PROJECT_NAME.serviceSet up the Caddy server
~/Caddyfile
Append this:
http://$PROJECT_DOMAIN {
bind unix/.$PROJECT_DOMAIN.webserver.sock|777
handle /static* {
root * $HOME/static/$PROJECT_NAME
file_server
}
handle /media* {
root * $HOME/static/$PROJECT_NAME
file_server
}
reverse_proxy :$PORT
}Restart Caddy service
systemctl --user restart caddy.serviceCheck it out
Go to $PROJECT_DOMAIN in your browser.
First load takes some time as Caddy needs to get a certificate from Let's Encrypt.
It should probably work. If not, please Dungeon Master @twonum who is at fault for writing this guide.
Avoiding hiccups
Make sure that it continues to work; if not, there's probably something wrong with the config.