aboutsummaryrefslogtreecommitdiffstats
path: root/docs/deployment.md
blob: e561b5d0d1ef9ca38f819fd08fceca08121c0185 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# Deployment
The default Dockerfile should do a good job at running a solid web server that
automatically adjusts its worker count based on traffic. This is managed by
uWSGI. You need to configure the `DATABASE_URL` and `SECRET_KEY` variables. If
you want to deploy to a different host than the default, configure the
`ALLOWED_HOSTS` variable.

## Static file hosting
You can either collect the static files in the container and use uWSGI to host
them, or put them on your host and manage them through a web server running on
the host like nginx.

## Database migrations
To bring the schema up-to-date, first stop an existing database container, then
start a container that just runs the migrations and exits, and then starts the
main container off the new container again.

## Ansible task
An example Ansible task to deploy the site is shown below, it should read fairly
humanly and give you a rough idea of steps needed to deploy the site.

```yml
- name: ensure the `{{ pysite_pg_username }}` postgres user exists
  become: yes
  become_user: postgres
  postgresql_user:
    name: "{{ pysite_pg_username }}"
    password: "{{ pysite_pg_password }}"
  when: pysite_pg_host == 'localhost'

- name: ensure the `{{ pysite_pg_database }}` postgres database exists
  become: yes
  become_user: postgres
  postgresql_db:
    name: "{{ pysite_pg_database }}"
    owner: "{{ pysite_pg_username }}"
  when: pysite_pg_host == 'localhost'

- name: ensure the `{{ pysite_hub_repository }}` image is up-to-date
  become: yes
  docker_image:
    name: "{{ pysite_hub_repository }}"
    force: yes

- name: ensure the nginx HTTP vhosts are up-to-date
  become: yes
  template:
    src: "nginx/{{ item.key }}.http.conf.j2"
    dest: "/etc/nginx/sites-available/{{ item.value }}.http.conf"
  with_dict: "{{ pysite_domains }}"
  notify: reload nginx

- name: ensure the nginx HTTPS vhosts are up-to-date
  become: yes
  template:
    src: "nginx/{{ item.key }}.https.conf.j2"
    dest: "/etc/nginx/sites-available/{{ item.value }}.https.conf"
  with_dict: "{{ pysite_domains }}"
  notify: reload nginx

- name: ensure the nginx HTTP vhosts are symlinked to `/etc/nginx/sites-enabled`
  become: yes
  file:
    src: /etc/nginx/sites-available/{{ item.value }}.http.conf
    dest: /etc/nginx/sites-enabled/{{ item.value }}.http.conf
    state: link
  with_dict: "{{ pysite_domains }}"
  notify: reload nginx

- name: ensure we have HTTPS certificates
  include_role:
    name: thefinn93.letsencrypt
  vars:
    letsencrypt_cert_domains: "{{ pysite_domains | dict2items | map(attribute='value') | list }}"
    letsencrypt_email: "[email protected]"
    letsencrypt_renewal_command_args: '--renew-hook "systemctl restart nginx"'
    letsencrypt_webroot_path: /var/www/_letsencrypt

- name: ensure the nginx HTTPS vhosts are symlinked to `/etc/nginx/sites-enabled`
  become: yes
  file:
    src: /etc/nginx/sites-available/{{ item.value }}.https.conf
    dest: /etc/nginx/sites-enabled/{{ item.value }}.https.conf
    state: link
  with_dict: "{{ pysite_domains }}"
  notify: reload nginx

- name: ensure the web container is absent
  become: yes
  docker_container:
    name: pysite
    state: absent

- name: ensure the `{{ pysite_static_file_dir }}` directory exists
  become: yes
  file:
    path: "{{ pysite_static_file_dir }}"
    state: directory
    owner: root
    group: root

- name: collect static files
  become: yes
  docker_container:
    image: "{{ pysite_hub_repository }}"
    name: pysite-static-file-writer
    command: python manage.py collectstatic --noinput
    detach: no
    cleanup: yes
    network_mode: host
    env:
      DATABASE_URL: "{{ pysite_pg_database_url }}"
      SECRET_KEY: "me-dont-need-no-secret-key"
      STATIC_ROOT: "/html"
    volumes:
      - "/var/www/pythondiscord.com:/html"

- name: ensure the database schema is up-to-date
  become: yes
  docker_container:
    image: "{{ pysite_hub_repository }}"
    name: pysite-migrator
    detach: no
    cleanup: yes
    command: python manage.py migrate
    network_mode: host
    env:
      DATABASE_URL: "postgres://{{ pysite_pg_username }}:{{ pysite_pg_password }}@{{ pysite_pg_host }}/{{ pysite_pg_database }}"
      SECRET_KEY: "me-dont-need-no-secret-key"

- name: ensure the website container is started
  become: yes
  docker_container:
    image: "{{ pysite_hub_repository }}"
    name: pysite
    network_mode: host
    restart: yes
    restart_policy: unless-stopped
    ports:
      - "127.0.0.1:4000:4000"
    env:
      ALLOWED_HOSTS: "{{ pysite_domains | dict2items | map(attribute='value') | join(',') }}"
      DATABASE_URL: "postgres://{{ pysite_pg_username }}:{{ pysite_pg_password }}@{{ pysite_pg_host }}/{{ pysite_pg_database }}"
      PARENT_HOST: pysite.example.com
      SECRET_KEY: "{{ pysite_secret_key }}"
```