Anonymous
Not logged in
Talk
Contributions
Log in
Request account
Rest of What I Know
Search
Editing
One Quick Way To Host A WebApp
(section)
From Rest of What I Know
Namespaces
Page
Discussion
More
More
Page actions
Read
Edit
History
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
== The App == === The App Itself === The first thing I try to do is get the app running one way or the other. This part is easy and I just iterate locally on my dev machine (a Macbook). Once I've got it running, I try to encode that state into a Docker container. I use [https://orbstack.dev/ Orbstack] to develop on MacOS because it's far more convenient than Docker proper. This is not-technically required, but since I have low usage, I can pack more things onto a single box with Docker since I don't have to worry about environments clobbering each other. It's also not usually too hard. === Database === If you're writing a personal application, SQLite is the best tool to reach for. It's a single file and it's easy to deal with. If you're also hosting other things, like I am with Mediawiki, it is worth running your distribution's default MySQL/PostgreSQL on the host itself. If you're going to use Docker later, remember that users will need to be granted permissions to log in from the Docker/Podman network too (which is often 10.x.x.x). === Hosting Your Code === You can build your app and only deploy the artifacts, but I've found it much easier to just push the source code and build the app on the production host. The way I do this is perhaps unorthodox or perhaps standard. First, create your app directory: mkdir ~/sub.roshangeorge.dev Then make it into a git repo: cd ~/sub.roshangeorge.dev git init . Then on your dev machine, add it as a remote: git remote add deploy user@host:~/sub.roshangeorge.dev git push deploy main Now, this would seem to be enough, but you can't push to a branch that is checked out. So what I do is that I only ever have 'releases' checked out. And a release is just a tag. So on my local machine, I might do. git tag release-20250313 git push deploy --tags And then on the host I check out the relevant tag when I want to run that: git checkout release-20250313 This little trick lets me just `git push` when I want to deploy. If you like, you can put in post-commit hooks and all that, but I haven't found them useful. I just ssh on and build. === Building === I like to have, in each of my repos, a `bin/build` script. For the ones with Dockerfiles these look as simple as: #!/usr/bin/env bash podman build -t sub.roshangeorge.dev:latest . A convenience thing I have throughout is that I refer to the app everywhere by some canonical subdomain. The docker tag is the subdomain, the nginx site is the subdomain name, the code directory is the subdomain. Everything is the subdomain. I also have a `bin/backup` that describes how to backup. === Containerization === I prefer using Linux containers for everything because they pins the environment but also because they retain app environments in their own worlds. Mediawiki, which this site is based on, is written in PHP and composer (a PHP package manager) just goes everywhere. With docker/podman, all that is contained in one place. Other languages don't do this as much. For example, Rust's `cargo` is quite well-contained in that it runs inside a user-context. So I build Rust programs in their own directory. I used to run Python programs in containers exclusively but with `mise` and `uv` it is possible to run them raw. I still prefer to run them in containers, though. I just have to remember to make them listen on a unique port, and to expose the port. ==== Quadlets ==== I used to use Podman and then write a traditional systemd unit file to manage the container as if it were a service. I liked that approach because the fact that it was in podman was an implementation detail of the app. I could just as well have it a bare binary doing the same thing and I'd run the same `systemctl` command. I later discovered systemd quadlets: containers managed by systemd itself. These are quite nice since you write them in a format that is like a unit file and you can use your usual systemctl commands to manage them directly. Here's an example quadlet configuration. <syntaxhighlight lang="ini"> [Unit] Description=MediaWiki Container After=network-online.target Wants=network-online.target [Container] Image=localhost/wiki.roshangeorge.dev:latest ContainerName=rowik Environment=MEDIAWIKI_DB_TYPE=mysql PublishPort=127.0.0.1:8080:80 Volume=/mnt/r2/uploads:/var/www/html/images:Z AutoUpdate=registry [Service] Restart=always TimeoutStartSec=300 [Install] WantedBy=multi-user.target default.target </syntaxhighlight> One debug annoyance is that viewing quadlet startup logs is not straightforward if you have made an error in quadlet configuration. Instead of an error in your service, it is a syslog error on the unit file generator. This is because when a quadlet is configured like this, what happens is that systemd has a generator that converts it to a service file on the fly and then runs that, so if you have a configuration error here it isn't marked against the service and so you'll just get a 'service not found'. Regardless, I find quadlets nicer to manage than writing a run script and a systemd unit file around that script. ==== Docker / Podman ==== The default thing I used to do was build the Docker image on the server and just run them there. When you do that, you can use docker/podman for process management or you can use systemd to manage your docker/podman commands. I prefer using systemd because then I don't have to remember which parts are managed with systemd and which ones with docker. One thing to note with podman is that you have to fully specify the name of parent images unless you configure the default to docker.io. Fully specifying the parent images is more portable in that you just copy the Dockerfile elsewhere and it will still build. Here's an example Dockerfile for my wiki. <syntaxhighlight lang="docker"> FROM docker.io/mediawiki:latest RUN apt-get update && apt-get install -y lua5.4 sendmail COPY ./apache.conf /etc/apache2/sites-available/000-default.conf WORKDIR /var/www/html COPY custom-php.ini /usr/local/etc/php/conf.d/custom-php.ini # Copy any additional directories (extensions, skins, etc.) COPY extensions/ ./extensions/ COPY robots.txt ./robots.txt RUN ln -sf images uploads COPY LocalSettings.php ./LocalSettings.php # update if possible (so migrations etc. can run) RUN php maintenance/update.php --quick # generate the sitemap (so that google can find sub-pages) RUN php maintenance/run.php generateSitemap --compress=no --identifier=sitemap RUN cp sitemap-index-sitemap.xml sitemap.xml </syntaxhighlight> ===== Process Management ===== With docker/podman, you can just set them to restart or whatever whenever you like and things will stay perpetually running, but I like a single system for all my apps, whether docker or not, and so I use systemd to manage them. An example systemd file looks like this for a docker app: <syntaxhighlight lang="ini"> [Unit] Description=App Container After=network.target [Service] Type=simple User=ubuntu ExecStart=/usr/bin/podman run --name sub_roshangeorge_dev --mount type=bind,source=/mnt/r2/files,target=/var/www/files -e ENV="var" -p 127.0.0.1:8080:80 sub.roshangeorge.dev:latest ExecStop=/usr/bin/podman stop sub_roshangeorge_dev ExecStopPost=/usr/bin/podman rm sub_roshangeorge_dev Restart=always [Install] WantedBy=multi-user.target </syntaxhighlight> But you could just as well have a simple non-docker version (this one is a mail server not a web-app but the idea applies) <syntaxhighlight lang="ini"> [Unit] Description=Superheap RSS Service After=network.target [Service] Type=simple User=ubuntu ExecStart=/home/ubuntu/superheap/target/release/superheap serve -c /home/ubuntu/rss/superheap.json Restart=always RestartSec=5 WorkingDirectory=/home/ubuntu [Install] WantedBy=multi-user.target </syntaxhighlight> While you'll want to symlink these into either `/etc/systemd/system/sub.roshangeorge.dev.service` or `~/.config/systemd/user/sub.roshangeorge.dev.service` if you want to keep them entirely home directory constrained, you can just leave the service unit file in your repo and have it symlinked in. Because your entire repo is on the host, you can just embed all of the host configs in your repo and then appropriately symlink them. ==== Registry ==== Originally, I used to just build the docker images on the server itself. This was simply out of convenience. After some time, it got to be a bit of a hassle and I found myself doing the annoying thing of just writing code on the server and building the image there. Well, that's a bit dopey, but it's very easy to fix. You can run a registry container on the server and just push images to it when you're ready. <syntaxhighlight lang="ini"> ubuntu@kant:~$ cat ~/.config/containers/systemd/registry.container [Unit] Description=Container Registry After=network-online.target Wants=network-online.target [Container] Image=docker.io/library/registry:2 PublishPort=5000:5000 Volume=registry-data:/var/lib/registry AutoUpdate=registry [Service] Restart=always TimeoutStartSec=900 [Install] WantedBy=default.target </syntaxhighlight> That's a systemd quadlet that runs the registry. I do the usual `docker build --platform=linux/amd64 .` on my Mac and then the `docker tag registry.blahblah.com` and then `docker push` to it and it all works just like you'd expect.
Summary:
Please note that all contributions to Rest of What I Know are considered to be released under the Creative Commons Attribution-ShareAlike (see
Rest of What I Know:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Navigation
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Wiki tools
Wiki tools
Special pages
Page tools
Page tools
User page tools
More
What links here
Related changes
Page information
Page logs