diff --git a/README.md b/README.md
index 2d13ac3..236d62c 100644
--- a/README.md
+++ b/README.md
@@ -13,17 +13,44 @@ Scheduling for TF2
OpenAPI documentation
- [Flask-Migrate](https://flask-migrate.readthedocs.io/en/latest/)
(Alembic) for database migrations
+ - [Celery](https://docs.celeryproject.org/en/stable/) for async tasks
+ - [Redis](https://redis.io/) for Celery broker
- **Database:** [PostgreSQL 17.1](https://www.postgresql.org/docs/17/index.html)
(production) / SQLite (development)
## Setup (dev)
```sh
+docker compose build
docker compose up
+DATABASE_URI=sqlite:///db.sqlite3 flask db upgrade
```
App will run at port 8000.
+## Setup (production)
+
+Build the frontend app:
+
+```sh
+cd availabili.tf
+npm run build
+```
+
+Build the rest of the containers:
+
+```sh
+docker compose -f docker-compose.prod.yml build
+docker compose -f docker-compose.prod.yml up
+```
+
+Perform initial database migration:
+
+```sh
+docker exec -it backend bash
+flask db upgrade
+```
+
## OpenAPI
The backend will automatically serve its OpenAPI-compliant spec at
diff --git a/availabili.tf/Dockerfile.prod b/availabili.tf/Dockerfile.prod
new file mode 100644
index 0000000..40a98b3
--- /dev/null
+++ b/availabili.tf/Dockerfile.prod
@@ -0,0 +1,7 @@
+FROM steebchen/nginx-spa:stable
+
+COPY dist/ /app
+
+EXPOSE 80
+
+CMD ["nginx"]
diff --git a/availabili.tf/src/components/AvailabilityComboBox.vue b/availabili.tf/src/components/AvailabilityComboBox.vue
deleted file mode 100644
index 611ed04..0000000
--- a/availabili.tf/src/components/AvailabilityComboBox.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/availabili.tf/src/components/AvailabilityGrid.vue b/availabili.tf/src/components/AvailabilityGrid.vue
index cd1b763..1866ad9 100644
--- a/availabili.tf/src/components/AvailabilityGrid.vue
+++ b/availabili.tf/src/components/AvailabilityGrid.vue
@@ -1,37 +1,37 @@
diff --git a/availabili.tf/src/components/IntegrationDetails.vue b/availabili.tf/src/components/IntegrationDetails.vue
deleted file mode 100644
index 86f9694..0000000
--- a/availabili.tf/src/components/IntegrationDetails.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
- Discord Integration
-
- (id: {{ props.integration.id }})
-
-
-
-
-
Webhook URL
-
-
-
-
-
-
-
-
-
-
-
diff --git a/availabili.tf/src/components/PlayerTeamCard.vue b/availabili.tf/src/components/PlayerTeamCard.vue
index 3491699..944cbab 100644
--- a/availabili.tf/src/components/PlayerTeamCard.vue
+++ b/availabili.tf/src/components/PlayerTeamCard.vue
@@ -3,6 +3,7 @@ import { computed, type PropType, ref, watch } from "vue";
import { useTeamsStore } from "../stores/teams";
import { useRosterStore } from "../stores/roster";
import { type ViewTeamMembersResponse, type TeamSchema, type RoleSchema } from "@/client";
+// @ts-expect-error
import SvgIcon from "@jamescoyle/vue-icon";
import { mdiCrown } from "@mdi/js";
import RoleTag from "../components/RoleTag.vue";
diff --git a/availabili.tf/src/components/RoleTag.vue b/availabili.tf/src/components/RoleTag.vue
index 4db2a6c..62aa4fb 100644
--- a/availabili.tf/src/components/RoleTag.vue
+++ b/availabili.tf/src/components/RoleTag.vue
@@ -1,17 +1,17 @@
-
-
-
-
- Roster for Snus Brotherhood
- Aug. 13, 2036 @ 11:30 PM EST
-
-
-
-
-
Available
-
-
- No players are currently available for this role.
-
-
-
-
Available if needed
-
-
-
-
-
-
-
diff --git a/availabili.tf/src/views/ScheduleView.vue b/availabili.tf/src/views/ScheduleView.vue
index 0e0e31c..5c67d73 100644
--- a/availabili.tf/src/views/ScheduleView.vue
+++ b/availabili.tf/src/views/ScheduleView.vue
@@ -1,6 +1,5 @@