Jed Rembold
May 19/20, 2026
The program usually used to do so is called
ssh, standing for “secure shell”
To log into a remote server, the command looks something like:
ssh {username}@{ip address or domain name}
where
username is your username on the
remote server (which may be different than your local
username)ip address or domain name is either the
direct ip address of the server (eg. 165.213.13.194) or the domain name
(myserver.net)-pTo create a new key, you can use
ssh-keygen -t ed25519 -C {desc comment}
.ssh folder in your home directory: one
with just id_ed25519 and one with
id_ed25519.pubJust creating the keys doesn’t do anything special. You need to copy the public key over to whatever server you want to connect to.
To copy the public key over to the desired server in Linux/MacOS:
ssh-copy-id {username}@{servername}In Windows:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh {username}@{servername} "cat >> ~/.ssh/authorized_keys"
You’ll need to enter in your password one more time, but then the key contents will be copied over
Often, you are connecting to the same servers again and again
Instead, you can set up “profiles” in your
{HOME}/.ssh/config file
A general profile entry might look something like:
Host {profile_name}
User {username}
HostName {domain name or ip address}
Port {port, if not default}There are more options and settings that can be configured. See
man ssh_config.
scp: The
scp command combines normal
cp and ssh,
allowing you to include a remote server in the standard format
scp local_file remote_server:remote_filersync: Syncs entire folders if
desired, only transferring what information is necessary
rsync -avP local_folder/ remote_server:remote_pathUse GitHub as an intermediatary. Write and push changes from your local system to GitHub, and then pull them down to the remote.
There are technically two types of tunnels, but the type that will be most useful to us are called local tunnels
Connect a resource that is available on the remote to a location on our local system
Requires adding an optional flag when calling ssh:
ssh -L {local_dest}:{remote_target} {user}@{remote}
localhost:8080localhost:5432If you always want a tunnel to be created when you connect to a remote, you can set this up in your SSH config!
Host {profile_name}
User {username}
HostName {domain name or ip address}
Port {port, if not default}
LocalForward {local port} {remote target}You can create as many tunnels as you might need!
From the CLI:
ssh -J {user}@{jumphost} {user}@{service}From SSH Config
Host {your profile name}
Hostname {service}
User {user}
ProxyJump {user}@{jumphost}
ProxyJump can also just
point to another profile nameI emailed you all earlier with a server address and login information. Use that to work through the following:
passwd. Note that when you type in
passwords on most shells, they will not show anything
for security but are indeed recording what you type..ssh/config file to facilitate connecting to
this serverdocker-compose.yml file to declare
everything that should happenservices,
volumes,
networksimage, ports,
environment variables, etc.Docker run:
docker run -p 8080:80 nginx:alpineDocker Compose:
services:
web:
image: nginx:alpine
ports:
- "8080:80"servicesimage: {name} - the name of the Docker
image you want the service to be based onbuild: . - build an image from a
Dockerfile present in this directorycontainer-name: {name} - an optional
more meaningful container nameports: - a list of port mappings from
host to containervolumes: - a list of volume mappings
from host to container. Can be to local directories or to a more
abstract volumeenvironment: - a collection of
environment variablesdepends_on: - list of other services
this service depends on-key: value pairingsversion key at the top. That is no longer
necessary, but you’ll still see it in many online Compose filesA named volume essentially sets aside some space (and gives it a name) to be managed by Docker
This space will persist across container restarts/rebuilds, and host reboots
Best for long running persistence
services:
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Bind mounts are basically akin to what we can do with the
-v flag when using
docker run
Maps a local directory on the host to a directory in the container
More tightly controls where on the host the data is stored
Especially useful if you want to be able to add data to a folder and then have it accessible inside the container
services:
db:
image: postgres:17
volumes:
- ./pgdata:/var/lib/postgresql/data
- ./mntdata:/mnt/dataWhen you launch containers using Docker Compose, it automatically creates an isolated network upon start that all the containers belong to
Containers are discoverable to one another: just using their service name as that hostname
Inside the container, localhost means
that container, not the host system.
If you want to access content exposed inside a container, you must set up a port mapping
services:
db:
image: postgres:17
ports:
- "5050:5432"docker-compose.yml file:
docker compose up starts everything up,
with logging in the foregrounddocker compose up -d starts everything
up detached (in the background)docker compose down stops and removes
containersdocker compose up --build will rebuild
anything that used a custom Dockerfiledocker compose restart {service} will
restart a specific service from the stack| State | Town | Name |
|---|---|---|
| OR | Salem | Jed |
| OR | Salem | Kristen |
| OR | Portland | Calvin |
| OR | Portland | Hank |
| OR | Portland | Rachel |
| CA | Los Angeles | Todd |
| Type | Description |
|---|---|
BOOLEAN |
True/False |
INT32 |
32-bit signed integer |
INT64 |
64-bit signed integer |
FLOAT |
32-bit float |
DOUBLE |
64-bit float |
BYTE_ARRAY |
Binary blobs or UTF-8 strings |
| Type | Backed by | Description |
|---|---|---|
UTF8 |
BYTE_ARRAY |
Strings |
DECIMAL |
INT or
BYTE_ARRAY |
high-precision (fixed) floats |
DATE |
INT32 |
Days since Unix epoch |
TIME_MILLIS |
INT32 |
Time of day (ms precision) |
TIMESTAMP_MICROS |
INT64 |
DateTime with ms precision |
LIST |
Repeated fields | Arrays/lists |
MAP |
Nested fields | Dictionaries |
INT31 in Parquet unless forced to
BOOLEANGROUP BY works actuallyIn Python with Pandas (needs
pyarrow)
df = pd.DataFrame({'name': ['Alice', 'Bob'], 'score': [95, 100]})
df.to_parquet('students.parquet', engine='pyarrow')
df2 = pd.read_parquet('students.parquet', engine='pyarrow')In R (needs the arrow or
nanoparquet library)
df <- data.frame(name = c("Alice", "Bob"), score=c(95,100))
write_parquet(df, "students.parquet")
df2 <- read_parquet("students.parquet")import pyarrow.parquet as pq
df = pq.ParquetFile(filename)
df.metadata
library(nanoparquet)
ds <- read_parquet_metadata(filename)