├── .gitignore ├── LICENSE ├── Readme.md ├── default.nix ├── examples ├── Readme.md ├── django-keys ├── djangoproject │ ├── djangoproject │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── settings_nix.py │ │ ├── urls.py │ │ └── wsgi.py │ └── manage.py ├── nixops │ ├── hetznercloud.nix │ └── minimal.nix └── nixos │ ├── custom-python.nix │ ├── minimal.nix │ └── nignx-letsencrypt-duckdns.nix ├── nixpkgs-src.nix ├── python.nix ├── shell.nix └── static-files.nix /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | examples/nixops/test.nix 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 DavHau 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # NixOS-based Django deployment 2 | !! WARNING !! This project has not been updated for a while. You can still use this as a template, but make sure to update the nixpkgs version in `nixpkgs-src.nix` 3 | 4 | This Project aims to provide a production grade NixOS configuration for Django projects. By taking your source code and some parameters as input it will return a nixos configuration which serves your Django project. 5 | 6 | An exemplary django project with some example NixOS/NixOps configs can be found under `./examples` 7 | 8 | ## What you will get 9 | - A PostgreSQL DB with access configured for django 10 | - A systemd service which serves the project via gunicorn 11 | - A defined way of passing secrets to Django without leaking them into /nix/store 12 | - Your static files as a separated build artifact (by default served via whitenoise) 13 | - Ability to configure some common options like (allowed-hosts, port, processes, threads) through your nix config. 14 | - Having your `manage.py` globally callable via `manage-projectname` (only via root/sudo) 15 | 16 | 17 | ## Parameters 18 | ```nix 19 | { # MANDATORY 20 | name, # create a name for the project 21 | keys-file, # path to a file containing secrets 22 | src, # derivation of django source code 23 | 24 | # OPTIONAL 25 | settings, # django settings module like `myproject.settings` 26 | pkgs ? import ./nixpkgs-src.nix { config = {}; }, # nixpkgs 27 | python ? import ./python.nix { inherit pkgs; }, # python + modules 28 | manage-py ? "${src}/manage.py", # path to manage.py inside src 29 | static-files ? (import ./static-files.nix { # derivation of static files 30 | inherit pkgs python src settings name manage-py; 31 | }), 32 | wsgi ? "${name}.wsgi", # django wsgi module like `myproject.wsgi` 33 | processes ? 5, # number of proccesses for gunicorn server 34 | threads ? 5, # number of threads for gunicorn server 35 | db-name ? name, # database name 36 | user ? "django", # system user for django 37 | port ? 80, # port to bind the http server 38 | allowed-hosts ? "*", # string of comma separated hosts 39 | ... 40 | }: 41 | ``` 42 | 43 | 44 | 45 | ## Prerequisites 46 | Django settings must be configured to: 47 | - load `SECRET_KEY` and `STATIC_ROOT` from the environment: 48 | ```python 49 | SECRET_KEY=environ.get('SECRET_KEY') 50 | STATIC_ROOT=environ.get('STATIC_ROOT') 51 | ``` 52 | - load `ALLOWED_HOSTS` from a comma separated list environment variable: 53 | ```python 54 | ALLOWED_HOSTS = list(environ.get('ALLOWED_HOSTS', default='').split(',')) 55 | ``` 56 | - use exactly this `DATABASES` configuration: 57 | ```python 58 | DATABASES = { 59 | 'default': { 60 | 'ENGINE': 'django.db.backends.postgresql', 61 | 'NAME': environ.get('DB_NAME'), 62 | 'HOST': '', 63 | } 64 | } 65 | ``` 66 | 67 | To serve static files out of the box, include the whitenoise middleware: 68 | ```python 69 | MIDDLEWARE += [ 'whitenoise.middleware.WhiteNoiseMiddleware' ] 70 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage' 71 | ``` 72 | 73 | (See `./examples/djangoproject/djangoproject/settings_nix.py` for full example) 74 | 75 | 76 | ## Secrets / Keys 77 | To pass secrets to django securely: 78 | 1. Create a file containing your secrets as environment variables like this: 79 | ``` 80 | export SECRET_KEY="foo" 81 | export ANOTHER_SECRET_FOR_DJANGO="bar" 82 | ``` 83 | 2. Pass the path of the file via parameter `keys-file` 84 | This file will not be managed by nix. 85 | If you are deploying to a remote host, make sure this file is available. An example on how to do this with NixOps can be found under `./examples/nixops` 86 | 87 | A systemd service running as root will later pick up that file and copy it to a destination under `/run/` where only the django system user can read it. Make sure by yourself to protect the source file you uploaded to the remote host with proper permissions or use the provided NixOps example. 88 | 89 | ## Examples 90 | See `Readme.md` inside `./examples` 91 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { # MANDATORY 2 | name, # create a name for the project 3 | keys-file, # path to a file containing secrets 4 | src, # derivation of django source code 5 | 6 | # OPTIONAL 7 | settings, # django settings module like `myproject.settings` 8 | pkgs ? import ./nixpkgs-src.nix { config = {}; }, # nixpkgs 9 | python ? import ./python.nix { inherit pkgs; }, # python + modules 10 | manage-py ? "${src}/manage.py", # path to manage.py inside src 11 | static-files ? (import ./static-files.nix { # derivation of static files 12 | inherit pkgs python src settings name manage-py; 13 | }), 14 | wsgi ? "${name}.wsgi", # django wsgi module like `myproject.wsgi` 15 | processes ? 5, # number of proccesses for gunicorn server 16 | threads ? 5, # number of threads for gunicorn server 17 | db-name ? name, # database name 18 | user ? "django", # system user for django 19 | port ? 80, # port to bind the http server 20 | allowed-hosts ? "*", # string of comma separated hosts 21 | ... 22 | }: 23 | with pkgs; 24 | let 25 | load-django-env = '' 26 | export STATIC_ROOT=${static-files} 27 | export DJANGO_SETTINGS_MODULE=${settings} 28 | export ALLOWED_HOSTS=${allowed-hosts} 29 | export DB_NAME=${db-name} 30 | ''; 31 | load-django-keys = '' 32 | source /run/${user}/django-keys 33 | ''; 34 | manage-script-content = '' 35 | ${load-django-env} 36 | ${load-django-keys} 37 | ${python}/bin/python ${manage-py} $@ 38 | ''; 39 | manage = 40 | runCommand 41 | "manage-${name}-script" 42 | { propagatedBuildInputs = [ src python ]; } 43 | ''mkdir -p $out/bin 44 | bin=$out/bin/manage 45 | echo -e '${manage-script-content}' > $bin 46 | chmod +x $bin 47 | ''; 48 | manage-via-sudo = 49 | runCommand 50 | "manage-${name}" 51 | {} 52 | ''mkdir -p $out/bin 53 | bin=$out/bin/manage=${name} 54 | echo -e 'sudo -u ${user} bash ${manage}/bin/manage $@' > $bin 55 | chmod +x $bin 56 | ''; 57 | system-config = { 58 | # manage.py of the project can be called via manage-`projectname` 59 | environment.systemPackages = [ manage-via-sudo ]; 60 | 61 | # create django user 62 | users.users.${user} = {}; 63 | 64 | # The user of django.service might not have permission to access the keys-file. 65 | # Therefore we copy the keys-file to a place where django has access 66 | systemd.services.django-keys = { 67 | description = "Ensure keys are accessible for django"; 68 | wantedBy = [ "django.service" ]; 69 | requiredBy = [ "django.service" ]; 70 | before = [ "django.service" ]; 71 | serviceConfig = { Type = "oneshot"; }; 72 | script = '' 73 | mkdir -p /run/${user} 74 | touch /run/${user}/django-keys 75 | chmod 400 /run/${user}/django-keys 76 | chown -R ${user} /run/${user} 77 | cat ${keys-file} > /run/${user}/django-keys 78 | ''; 79 | }; 80 | 81 | # We name the service like the specified user. 82 | # This allows us to have multiple django projects running in parallel 83 | systemd.services.${user} = { 84 | description = "${name} django service"; 85 | wantedBy = [ "multi-user.target" ]; 86 | wants = [ "postgresql.service" ]; 87 | after = [ "network.target" "postgresql.service" ]; 88 | path = [ python src ]; 89 | serviceConfig = { 90 | LimitNOFILE = "99999"; 91 | LimitNPROC = "99999"; 92 | User = user; 93 | AmbientCapabilities = "CAP_NET_BIND_SERVICE"; # to be able to bind to low number ports 94 | }; 95 | script = '' 96 | ${load-django-env} 97 | ${load-django-keys} 98 | ${python}/bin/python ${manage-py} migrate 99 | ${python}/bin/gunicorn ${wsgi} \ 100 | --pythonpath ${src} \ 101 | -b 0.0.0.0:${toString port} \ 102 | --workers=${toString processes} \ 103 | --threads=${toString threads} 104 | ''; 105 | }; 106 | 107 | services.postgresql = { 108 | enable = true; 109 | ensureDatabases = [ db-name ]; 110 | ensureUsers = [{ 111 | name = "${user}"; 112 | ensurePermissions = { 113 | "DATABASE ${db-name}" = "ALL PRIVILEGES"; 114 | }; 115 | }]; 116 | package = pkgs.postgresql_11; 117 | }; 118 | }; 119 | in 120 | { 121 | inherit 122 | manage-via-sudo 123 | manage 124 | system-config 125 | load-django-env 126 | load-django-keys; 127 | } -------------------------------------------------------------------------------- /examples/Readme.md: -------------------------------------------------------------------------------- 1 | ## Contents 2 | 3 | ### `./djangoproject` 4 | Nearly untouched django project created via '`django-admin startproject djangoproject`'. 5 | Two changes have been made: 6 | 1. File `settings_nix.py` has been added which imports the default settings.py and adds the minimal required options necessary to be compatible to this project's nixos config. See parent dir's Readme for exact requirements for django settings 7 | 2. `urls.py` is modified to display the django admin page as frontpage. Otherwise we would get a `Not Found` error. 8 | 9 | ### `./nixos` 10 | Example NixOS configuration files. 11 | - `minimal.nix`: How to use this project 12 | - `custom-python.nix`: How to add extra python modules 13 | - `nginx-letsencrypt-duckdns.nix`: Secure nginx config with forced encryption + automatic letsencrypt + dynamic dns 14 | 15 | ### `./nixops` 16 | - `minimal.nix`: Equals `./nixos/minimal.nix` plus key management for NixOps. Extendable with examples from `./nixos`. 17 | - `hetznercloud.nix`: Concrete deployment example for a hetzner cloud instance which has been infected by https://github.com/elitak/nixos-infect -------------------------------------------------------------------------------- /examples/django-keys: -------------------------------------------------------------------------------- 1 | export SECRET_KEY="change_this_to_something_secure" -------------------------------------------------------------------------------- /examples/djangoproject/djangoproject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavHau/django-nixos/565a8abd8af6422517a8fcb93f75c7b0da1cfd8b/examples/djangoproject/djangoproject/__init__.py -------------------------------------------------------------------------------- /examples/djangoproject/djangoproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for djangoproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.10. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'vr@f-u7%x-m&--12wzg3rn!4#))0i)^3t40p^m@o(z1*-t)x0l' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | ] 41 | 42 | MIDDLEWARE = [ 43 | 'django.middleware.security.SecurityMiddleware', 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ] 51 | 52 | ROOT_URLCONF = 'djangoproject.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | 'django.template.context_processors.debug', 62 | 'django.template.context_processors.request', 63 | 'django.contrib.auth.context_processors.auth', 64 | 'django.contrib.messages.context_processors.messages', 65 | ], 66 | }, 67 | }, 68 | ] 69 | 70 | WSGI_APPLICATION = 'djangoproject.wsgi.application' 71 | 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 75 | 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.sqlite3', 79 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 80 | } 81 | } 82 | 83 | 84 | # Password validation 85 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 86 | 87 | AUTH_PASSWORD_VALIDATORS = [ 88 | { 89 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 90 | }, 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 99 | }, 100 | ] 101 | 102 | 103 | # Internationalization 104 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 105 | 106 | LANGUAGE_CODE = 'en-us' 107 | 108 | TIME_ZONE = 'UTC' 109 | 110 | USE_I18N = True 111 | 112 | USE_L10N = True 113 | 114 | USE_TZ = True 115 | 116 | 117 | # Static files (CSS, JavaScript, Images) 118 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 119 | 120 | STATIC_URL = '/static/' 121 | -------------------------------------------------------------------------------- /examples/djangoproject/djangoproject/settings_nix.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | from os import environ 3 | 4 | DEBUG = False 5 | 6 | # We load the secret key from the environment to not have it in /nix/store. 7 | SECRET_KEY=environ.get('SECRET_KEY') 8 | 9 | # The static root will be a path under /nix/store/ which we don't know yet. 10 | STATIC_ROOT=environ.get('STATIC_ROOT') 11 | 12 | # Allowed hosts are provided via nix config 13 | ALLOWED_HOSTS = list(environ.get('ALLOWED_HOSTS', default='').split(',')) 14 | 15 | ### Postgres Database Connection 16 | # We use a local (non TCP) DB connection by setting HOST to an empty string 17 | # In this mode the user gets authenticated via the OS. 18 | # Only processes of a specific system user will be able to access the DB 19 | DATABASES = { 20 | 'default': { 21 | 'ENGINE': 'django.db.backends.postgresql', 22 | 'NAME': environ.get('DB_NAME'), 23 | 'HOST': '' 24 | } 25 | } 26 | 27 | # We're using a python module to server static files. Scared of it? 28 | # Read here: http://whitenoise.evans.io/en/stable/index.html#infrequently-asked-questions 29 | MIDDLEWARE += [ 'whitenoise.middleware.WhiteNoiseMiddleware' ] 30 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage' 31 | -------------------------------------------------------------------------------- /examples/djangoproject/djangoproject/urls.py: -------------------------------------------------------------------------------- 1 | """djangoproject URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | urlpatterns = [ 20 | path('', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /examples/djangoproject/djangoproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for djangoproject project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoproject.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /examples/djangoproject/manage.py: -------------------------------------------------------------------------------- 1 | #!/nix/store/k5rdcbcwwpvj7l9f1yvd5mfggcfz16kk-python3-3.7.5/bin/python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoproject.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /examples/nixops/hetznercloud.nix: -------------------------------------------------------------------------------- 1 | { 2 | network.description = "Django Example Deployment"; 3 | 4 | webserver = 5 | { config, pkgs, ... }: 6 | { imports = [ 7 | 8 | ./minimal.nix 9 | ]; 10 | boot.loader.grub.device = "/dev/sda"; 11 | fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; }; 12 | boot.cleanTmpDir = true; 13 | networking.hostName = "django-example"; 14 | networking.firewall.allowPing = true; 15 | services.openssh.enable = true; 16 | users.users.root.openssh.authorizedKeys.keys = [ 17 | "your_ssh_key" 18 | ]; 19 | deployment.targetHost = "123.123.123.123"; 20 | }; 21 | } -------------------------------------------------------------------------------- /examples/nixops/minimal.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs; 3 | let 4 | destKeysDir = "/keys"; # define target key dir for nixops 5 | django = (import (builtins.fetchGit { 6 | url = "https://github.com/DavHau/django-nixos"; 7 | ref = "master"; 8 | ### uncomment next line and enter newest commit of https://github.com/DavHau/django-nixos 9 | # rev = "commit_hash"; 10 | })) { 11 | # Parameters of the particular django project 12 | inherit pkgs; 13 | name = "djangoproject"; 14 | keys-file = toString "${destKeysDir}/django-keys"; # path created by NixOps 15 | settings = "djangoproject.settings_nix"; 16 | src = "${../djangoproject}"; 17 | }; 18 | in 19 | { 20 | imports = [ django.system-config ]; 21 | # We upload the keys-file via NixOps' keys feature 22 | deployment.keys = { 23 | django-keys = { 24 | keyFile = ../django-keys; 25 | destDir = destKeysDir; 26 | }; 27 | }; 28 | networking.firewall.allowedTCPPorts = [ 80 ]; 29 | } -------------------------------------------------------------------------------- /examples/nixos/custom-python.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs; 3 | let django = (import (builtins.fetchGit { 4 | url = "https://github.com/DavHau/django-nixos"; 5 | ref = "master"; 6 | ### uncomment next line and enter newest commit of https://github.com/DavHau/django-nixos 7 | # rev = "commit_hash"; 8 | })) { 9 | inherit pkgs; 10 | name = "djangoproject"; 11 | keys-file = toString ../django-keys; 12 | settings = "djangoproject.settings_nix"; 13 | src = "${../djangoproject}"; 14 | python = pkgs.python37.withPackages ( ps: with ps; [ 15 | django_2_2 16 | whitenoise 17 | brotli 18 | gunicorn 19 | psycopg2 20 | requests # as an example we add requests 21 | ]); 22 | }; 23 | in 24 | { 25 | imports = [ django.system-config ]; 26 | } -------------------------------------------------------------------------------- /examples/nixos/minimal.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs; 3 | let django = (import (builtins.fetchGit { 4 | url = "https://github.com/DavHau/django-nixos"; 5 | ref = "master"; 6 | ### uncomment next line and enter newest commit of https://github.com/DavHau/django-nixos 7 | # rev = "commit_hash"; 8 | })) { 9 | inherit pkgs; 10 | name = "djangoproject"; 11 | keys-file = toString ../django-keys; 12 | settings = "djangoproject.settings_nix"; 13 | src = "${../djangoproject}"; 14 | }; 15 | in 16 | { 17 | imports = [ django.system-config ]; 18 | networking.firewall.allowedTCPPorts = [ 80 ]; 19 | } -------------------------------------------------------------------------------- /examples/nixos/nignx-letsencrypt-duckdns.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | with pkgs; 3 | let django = (import (builtins.fetchGit { 4 | url = "https://github.com/DavHau/django-nixos"; 5 | ref = "master"; 6 | ### uncomment next line and enter newest commit of https://github.com/DavHau/django-nixos 7 | # rev = "commit_hash"; 8 | })) { 9 | inherit pkgs; 10 | name = "djangoproject"; 11 | keys-file = toString ../django-keys; 12 | settings = "djangoproject.settings_nix"; 13 | src = "${../djangoproject}"; 14 | port = 8000; 15 | }; 16 | in 17 | { 18 | imports = [ django.system-config ]; 19 | networking.firewall.allowedTCPPorts = [ 80 443 ]; 20 | # nginx proxy 21 | services.nginx = { 22 | enable = true; 23 | # Use recommended settings 24 | recommendedGzipSettings = true; 25 | recommendedOptimisation = true; 26 | recommendedProxySettings = true; 27 | recommendedTlsSettings = true; 28 | # reverse proxy with automatic letsencrypt 29 | virtualHosts."example.com" = { 30 | enableACME = true; 31 | forceSSL = true; 32 | locations."/".proxyPass = "http://localhost:" + toString(8000) + "/"; 33 | # taking advantage of whitenoise's compression 34 | locations."/".extraConfig = ''proxy_set_header Accept-Encoding "br, gzip";''; 35 | }; 36 | }; 37 | services.ddclient = { 38 | enable = true; 39 | protocol = "duckdns"; 40 | password = "your_duckdns_token"; 41 | domains = ["example.duckdns.org"]; 42 | }; 43 | } -------------------------------------------------------------------------------- /nixpkgs-src.nix: -------------------------------------------------------------------------------- 1 | builtins.fetchGit { 2 | name = "nixpkgs-for-craifty"; 3 | url = https://github.com/nixos/nixpkgs-channels/; 4 | # `git ls-remote https://github.com/nixos/nixpkgs-channels nixos-19.09` 5 | ref = "refs/heads/nixos-19.09"; 6 | rev = "dca7ec628e55307ac4b46f00f3be09464fcf4f4b"; 7 | } -------------------------------------------------------------------------------- /python.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ...}: 2 | let 3 | python = pkgs.python37; 4 | in 5 | python.withPackages (ps: with ps; [ 6 | django_2_2 7 | whitenoise # for serving static files 8 | brotli # brotli compression for whitenoise 9 | gunicorn # for serving via http 10 | psycopg2 # for connecting to postgresql 11 | ]) 12 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs-src = import ./nixpkgs-src.nix; 3 | pkgs = import nixpkgs-src { config = {}; }; 4 | python = (import ./python.nix {inherit pkgs;}); 5 | in 6 | pkgs.mkShell { 7 | buildInputs = [ 8 | python 9 | pkgs.nixops 10 | ]; 11 | shellHook = '' 12 | export NIX_PATH="nixpkgs=${nixpkgs-src}:." 13 | export SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt 14 | ''; 15 | } 16 | -------------------------------------------------------------------------------- /static-files.nix: -------------------------------------------------------------------------------- 1 | { pkgs, 2 | src, 3 | python, 4 | settings, 5 | name ? "", 6 | manage-py ? "${src}/manage.py", 7 | ... }: 8 | 9 | pkgs.runCommand 10 | "${name}-static" 11 | { buildInputs = [ src python ]; } 12 | ''mkdir $out 13 | export SECRET_KEY="key" # collectstatic doesn't care about the key (with our whitenoise settings) 14 | export STATIC_ROOT=$out 15 | ${python}/bin/python ${manage-py} collectstatic --settings ${settings} 16 | '' --------------------------------------------------------------------------------