diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 0000000..fef94b6
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,29 @@
+name: "Build documentation"
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ docs:
+ name: "Build and deploy"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: ammaraskar/sphinx-action@master
+ with:
+ docs-folder: "docs/"
+
+ - uses: actions/upload-artifact@v1
+ with:
+ name: documentation-html
+ path: docs/_build/html/
+
+ - uses: shallwefootball/s3-upload-action@master
+ with:
+ aws_key_id: ${{ secrets.PALEWIRE_DOCS_AWS_ACCESS_KEY_ID }}
+ aws_secret_access_key: ${{ secrets.PALEWIRE_DOCS_AWS_SECRET_ACCESS_KEY }}
+ aws_bucket: ${{ secrets.PALEWIRE_DOCS_AWS_BUCKET }}
+ source_dir: docs/_build/html/
+ destination_dir: django-anss-archive
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..556b432
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+### Links
+
+* Docs: [palewi.re/docs/django-anss-archive/](https://palewi.re/docs/django-anss-archive/)
+* Code: [github.com/datadesk/django-anss-archive](https://github.com/datadesk/django-anss-archive/)
+* Issues: [github.com/datadesk/django-anss-archive/issues](https://github.com/datadesk/django-anss-archive/issues)
+* Packaging: [pypi.python.org/pypi/django-anss-archive](https://pypi.python.org/pypi/django-anss-archive)
+* Testing: [github.com/datadesk/django-anss-archive/actions](https://github.com/datadesk/django-anss-archive/actions)
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 8fe2f8f..0000000
--- a/README.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-django-anss-archive
-===================
-
-A Django application to archive real-time earthquake notifications from the `U.S. Geological Survey's Advanced National Seismic System `_
-
-
-Requirements
-------------
-
-* The Django web framework
-* A geospatial database like PostGIS
-
-
-Getting started
----------------
-
-Install the Python package.
-
-::
-
- $ pipenv install django-anss-archive
-
-Add to Django's INSTALLED_APPS. ::
-
- INSTALLED_APPS = (
- ...
- "anss",
- )
-
-Run migrations to create database tables. ::
-
- $ python manage.py migrate
-
-Run the archive command to save all earthquakes in the latest hour greater than 1.0 magnitude. ::
-
- $ python manage.py getlatestanssfeed
-
-Start your test server and visit the admin to see the results. ::
-
- $ python manage.py runserver
-
-It includes a list of all the earthquakes.
-
-.. image:: https://raw.githubusercontent.com/datadesk/django-anss-archive/master/img/list.png
-
-And lots of data about each one.
-
-.. image:: https://raw.githubusercontent.com/datadesk/django-anss-archive/master/img/detail.png
-
-
-Contributing
-------------
-
-Install dependencies for development. ::
-
- $ pipenv install --dev
-
-Run tests. ::
-
- $ make test
-
-Ship new version to PyPI. ::
-
- $ make ship
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
new file mode 100644
index 0000000..b6b8bd0
--- /dev/null
+++ b/docs/_static/css/custom.css
@@ -0,0 +1,234 @@
+@import url('https://fonts.googleapis.com/css2?family=Libre+Franklin:ital,wght@0,400;0,700;0,800;1,400;1,800&display=swap');
+
+div.document {
+ margin-top: 0;
+}
+
+div.body {
+ min-width: 0 !important;
+}
+
+div.body p, div.body li {
+ line-height: 1.45;
+}
+
+div.body li {
+ margin-bottom: 4px;
+}
+
+div.topic {
+ margin: 0;
+ padding: 8px 12px;
+}
+
+p.topic-title {
+ margin: 0;
+}
+
+div.topic ul {
+ margin-top: 6px;
+}
+@media screen and (max-width: 870px) {
+ ul {
+ margin: 10px 0 10px 20px;
+ }
+}
+
+div.sphinxsidebar h1.logo,
+div.sphinxsidebar h3 {
+ font-family: 'Libre Franklin', "Helvetica", "Liberation Sans", Arial, sans-serif;
+}
+
+div.sphinxsidebarwrapper h1.logo {
+ font-size: 1.85em;
+ font-weight: 800;
+}
+
+@media (min-width: 875px) {
+ div.sphinxsidebar {
+ margin-top: 85px;
+ }
+}
+
+@media screen and (max-width: 875px) {
+ div.sphinxsidebar {
+ width: auto;
+ padding: 10px 20px;
+ margin: 50px 0 0 0;
+ }
+}
+
+div.admonition {
+ padding: 15px;
+}
+
+div.warning {
+ background-color: #EEE;
+ border: 1px solid #CCC;
+}
+
+div.admonition p.admonition-title {
+ font-family: 'Libre Franklin', "Helvetica", "Liberation Sans", Arial, sans-serif;
+ margin: 0;
+}
+
+div.related nav {
+ padding-right: 10px;
+}
+
+body {
+ font-family: 'Libre Franklin', "Helvetica", "Liberation Sans", Arial, sans-serif;
+ font-size: 20px;
+ line-height: 1.45;
+ text-align: left;
+}
+@media screen and (max-width: 875px) {
+ body {
+ margin: 0;
+ padding: 0;
+ }
+ div.body > .section,
+ div.body > section {
+ padding: 0 20px;
+ }
+}
+
+@media (max-width: 550px) {
+ body {
+ font-size: 0.85em;
+ }
+}
+
+div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 {
+ font-family: 'Libre Franklin', "Helvetica", "Liberation Sans", Arial, sans-serif;
+ font-weight: 700;
+}
+
+div.body h1 {
+ font-weight: 800;
+ font-size: 220%;
+}
+
+pre {
+ padding: 7px;
+}
+
+.cell_output {
+ overflow: auto;
+}
+
+.toctree-wrapper p.caption {
+ font-weight: 700;
+}
+
+.toggle-details {
+ display: none;
+}
+
+.row {
+ width: 100%;
+ max-width: 660px;
+ margin: 0 auto;
+ overflow: hidden;
+}
+.row .fivecol {
+ width: 43%;
+ float: right;
+ min-height: 1px;
+}
+.row .sevencol {
+ width: 37%;
+ float: left;
+ min-height: 1px;
+}
+@media (max-width: 550px) {
+ .row .fivecol, .row .sevencol {
+ padding: 0 20px;
+ }
+}
+
+/* The header */
+nav {
+ display: block;
+ margin-top: 0.75em;
+ margin-bottom: 0.5em;
+ font-size: 1em;
+ line-height: 1.7em;
+}
+nav .shingle {
+ font-size: 1.7em;
+ font-weight: bold;
+ text-align: left;
+ padding-left: 0.15em;
+}
+nav .shingle a {
+ text-decoration: none;
+ font-weight: bold;
+ color: #000;
+}
+nav .shingle a:hover {
+ color: #686868;
+}
+@media (max-width: 550px) {
+ nav .shingle {
+ float: left;
+ margin-bottom: 5px;
+ padding-left: 0;
+ }
+}
+nav .links {
+ text-align: right;
+}
+nav .links ul {
+ float: right;
+ list-style-type: none;
+ margin: 4px 0 0 0;
+}
+nav .links li {
+ float: left;
+ list-style-position: inside;
+ margin-left: 0.5em;
+ font-size: 0.9em;
+ font-weight: 300;
+ margin-bottom: 0;
+}
+@media (max-width: 550px) {
+ nav .links li {
+ font-size: .825em;
+ }
+}
+nav .links li a {
+ text-decoration: none;
+}
+nav .links li a:hover {
+ text-decoration: underline;
+}
+.last {
+ margin-right: 0;
+}
+
+.topbar {
+ border-bottom: solid 10px #e5e5e5;
+ margin-bottom: 20px;
+ width: 100%;
+}
+
+a:link {
+ text-decoration: none;
+ color: #686868;
+}
+a:visited {
+ text-decoration: none;
+ color: #686868;
+}
+a:hover {
+ text-decoration: underline;
+}
+a:active {
+ text-decoration: underline;
+ color: #000;
+}
+
+.contents ul p {
+ margin-bottom: 0;
+}
diff --git a/img/detail.png b/docs/_static/img/detail.png
similarity index 100%
rename from img/detail.png
rename to docs/_static/img/detail.png
diff --git a/img/list.png b/docs/_static/img/list.png
similarity index 100%
rename from img/list.png
rename to docs/_static/img/list.png
diff --git a/docs/_templates/nav.html b/docs/_templates/nav.html
new file mode 100644
index 0000000..cd40a0e
--- /dev/null
+++ b/docs/_templates/nav.html
@@ -0,0 +1,49 @@
+
+
+
+
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..c005f1d
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,43 @@
+"""Configure Sphinx configuration."""
+import os
+import sys
+from datetime import datetime
+
+# Insert the parent directory into the path
+sys.path.insert(0, os.path.abspath(".."))
+
+extensions = [
+ "myst_parser",
+]
+templates_path = ["_templates"]
+source_suffix = ".rst"
+master_doc = "index"
+
+project = "django-anss-archive"
+year = datetime.now().year
+copyright = f"{year} Ben Welsh"
+
+exclude_patterns = ["_build"]
+
+html_theme = "alabaster"
+html_sidebars = {
+ "**": [
+ # "about.html",
+ # "navigation.html",
+ "relations.html",
+ "searchbox.html",
+ "donate.html",
+ ]
+}
+html_theme_options = {
+ "canonical_url": f"https://palewi.re/docs/{project}/",
+ "show_powered_by": False,
+ "show_relbar_bottom": True,
+}
+
+html_static_path = ["_static"]
+html_css_files = [
+ "css/custom.css",
+]
+
+pygments_style = "sphinx"
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..e0bdb53
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,78 @@
+```{include} _templates/nav.html
+```
+
+# django-anss-archive
+
+A Django application to archive real-time earthquake notifications from the [U.S. Geological Survey's Advanced National Seismic System](https://earthquake.usgs.gov/earthquakes/feed/)
+
+```{contents} Table of contents
+:local:
+:depth: 2
+```
+
+## Requirements
+
+- The Django web framework
+- A geospatial database like PostGIS
+
+## Getting started
+
+Install the Python package.
+
+```bash
+pipenv install django-anss-archive
+```
+
+Add to Django's INSTALLED_APPS.
+
+```python
+INSTALLED_APPS = ("anss",)
+```
+
+Run migrations to create database tables.
+
+```bash
+python manage.py migrate
+```
+
+Run the archive command to save all earthquakes in the latest hour greater than 1.0 magnitude.
+
+```bash
+python manage.py getlatestanssfeed
+```
+
+Start your test server and visit the admin to see the results.
+
+```bash
+python manage.py runserver
+```
+
+It includes a list of all the earthquakes.
+
+![list](_static/img/list.png)
+
+And lots of data about each one.
+
+![detail](_static/img/detail.png)
+
+## Contributing
+
+Install dependencies for development.
+
+```bash
+pipenv install --dev
+```
+
+Run tests.
+
+```bash
+pipenv run python setup.py test
+```
+
+## Links
+
+* Docs: [palewi.re/docs/django-anss-archive/](https://palewi.re/docs/django-anss-archive/)
+* Code: [github.com/datadesk/django-anss-archive](https://github.com/datadesk/django-anss-archive/)
+* Issues: [github.com/datadesk/django-anss-archive/issues](https://github.com/datadesk/django-anss-archive/issues)
+* Packaging: [pypi.python.org/pypi/django-anss-archive](https://pypi.python.org/pypi/django-anss-archive)
+* Testing: [github.com/datadesk/django-anss-archive/actions](https://github.com/datadesk/django-anss-archive/actions)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..32bb245
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..98a3c62
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+sphinx
+myst-parser