See invitation jayra.mattcaninto.space.
Moderate technical difficulty, but a fun project to work on. I wanted to create a unique and personalized wedding invitation website for Jayra and myself. The website is mobile-first, ensuring that it looks great* on mobile but sacrificing some desktop aesthetics.
The project involved setting up a custom Arch Linux server on DigitalOcean, deploying a web application on it, and using Docker via lazydocker for maintenance.
PostgreSQL was used for database management and an SSH into the DigitalOcean server created a proxy connection.
This was most definitely NOT the most efficient way to deploy. Updating the website requires more than a few steps: building project, pushing to Docker Hub and involved SSHing into the server, which introduces more friction than 24 grit sandpaper just to update the website.
Further, because I wanted to centralize config files locally, I also need a local compose.yaml and its accompanying secrets file that I have to maintain and keep in sync with the one on the server. Options I think might improve workflow:
- CI/CD pipeline using GitHub Actions to automate the build and deployment process whenever changes are pushed to the repository.
- Using static hosting services since current online service providers (e.g., github or cloudflare) have great static hosting options that can be easily integrated with an online hosted git repository.
A project I made after this one, Quartz Project, is deployed on a platform that allows for direct deployment from GitHub, which is much more efficient and easier to maintain and by that I mean, less friction to update and no config files to maintain.
Challenges and Solutions
Challenges and Solutions
- Linux.
- I had just recently switched to Linux at this point from Windows, so I had to learn how to use Linux. Referenced Arch Wiki, Unix and Linux System Administration Handbook 5ed (E. Nemeth, G. Snyder, T. Hein, B. Whaley), How Linux Works 3ed (B. Nissenbaum), The Linux Command Line by W. Shotts.
- Networking.
- I had to study networking concepts for this project since this would be my first project that i would publish live. Referenced Unix and Linux System Administration Handbook 5ed (E. Nemeth, G. Snyder, T. Hein, B. Whaley).
- React and ReactRouter.
- I had to learn how to use React Router for the frontend development of the website. Referenced official documentation.
- Cloudflare.
- Referenced official documentation.
- Docker.
- Referenced TechWorld with Nana and official documentation.
- PostgreSQL.
- Referenced official documentation.
- Drizzle as an ORM for PostgreSQL.
- Referenced official documentation.
Tech Stack
- Docker for deployment
- lazydocker by Jesse Duffield - a lazier way to manage everything docker.
- PostgreSQL for database management (messages from guests)
- Cloudflare for DNS, CDN, tunneling into VPS
- DigitalOcean Custom Bucket using Arch Linux LTS.
- used .qcow2 image to create a custom image:
- find mirrorlist here Offical Arch Linux Boxes
- i used: https://linuximages.de/openstack/arch/
- used .qcow2 image to create a custom image:
- ReactRouter for frontend development
Deployment Pipeline
- Build docker image locally
- Push to Docker Hub using private repository
- Pull image on server and run container
Connecting to Database for messages
- SSH configured for ProxyCommand allowing local port forwarding to the database on the server.
- Connect to database using BeeKeeper (or really even VSCode Extensions such as SQLTools) with the forwarded port.
Code Snippets
SSH Config for ProxyCommand and Local Port Forwarding
Host 1-arch-diocean-tunnel
User arch
ProxyCommand /usr/bin/cloudflared access ssh --hostname ${DOMAIN_WITH_SSH_ACCESS}
LocalForward ${LOCAL_PORT} 127.0.0.1:${REMOTE_PORT}Docker Compose File maintained locally and pushed to server as well
services:
app:
image: ${APP_IMAGE}
restart: always
ports:
- ${APP_PORT}
environment:
- DATABASE_URL=postgres://matt:ml@db:${PORT}/${DB}
- R2_ACCT_ID=${R2_ACCT_ID}
- R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
- R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
- R2_TOKEN_VALUE=${R2_TOKEN_VALUE}
- R2_PUBLIC_URL=${R2_PUBLIC_URL}
depends_on:
- db
pull_policy: always
db:
image: postgres:17
restart: always
environment:
POSTGRES_USER: ${USER}
POSTGRES_PASSWORD: ${PASS}
POSTGRES_DB: ${DB}
volumes:
- db_data:/var/lib/postgresql/data
ports:
- ${DB_PORT}
volumes:
db_data:
external: true
name: winvite_db_data